2019独角兽企业重金招聘Python工程师标准>>>

上篇文章<轻触开源-Java泛型Type类型的应用和实践(一)>

https://my.oschina.net/u/874727/blog/747427

非墨写到JAVA的泛型机制,被用到很多的开源项目。在众多的开源项目中,Gson是很具有代表性的一个。Gson是Google公司编写的一套用于Json数据转化为Java对象的一套通用工具库。之所以说它是通用工具库,是因为它的实现代码全部基于最基础的Java运行时环境,而不依赖于任何系统平台,也就是说你不仅可以在J2EE项目中应用它,你一样可以很容易的在Android,J2ME等等平台中直接应用它。

Gson跟很多的开源操纵了Java内部数据类型的项目相同,为了方便记录类型数据,Gson会将Java原有的一套数据类型,转化为自己的内部数据类型。比如,在上一章我们提到的在Java泛型中记录类型的Class和Type类型,就被Gson转化为TypeToken。WildcardType转化为Gson自己的WildcardTypeImpl,GenericArrayType转为了Gson的内部类型GenericArrayTypeImpl。而这些类型的定义都被记录在com.google.gson.internal包中。我们从这个包名也看的很明白,就是Gson系统将一些转换的细节屏蔽到Gson项目的内部,而只暴露给用户一些简单的接口。

但不论Gson如何转变既定的Java类型,实际上都只是在Java的既定类型外加一层壳,可以说是一个类适配器,比如我们来看一下WildcardTypeImpl的代码:

 private static final class WildcardTypeImpl implements WildcardType, Serializable {private final Type upperBound;private final Type lowerBound;public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {checkArgument(lowerBounds.length <= 1);checkArgument(upperBounds.length == 1);if (lowerBounds.length == 1) {checkNotNull(lowerBounds[0]);checkNotPrimitive(lowerBounds[0]);checkArgument(upperBounds[0] == Object.class);this.lowerBound = canonicalize(lowerBounds[0]);this.upperBound = Object.class;} else {checkNotNull(upperBounds[0]);checkNotPrimitive(upperBounds[0]);this.lowerBound = null;this.upperBound = canonicalize(upperBounds[0]);}}public Type[] getUpperBounds() {return new Type[] { upperBound };}public Type[] getLowerBounds() {return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY;}@Override public boolean equals(Object other) {return other instanceof WildcardType&& $Gson$Types.equals(this, (WildcardType) other);}@Override public int hashCode() {// this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());return (lowerBound != null ? 31 + lowerBound.hashCode() : 1)^ (31 + upperBound.hashCode());}@Override public String toString() {if (lowerBound != null) {return "? super " + typeToString(lowerBound);} else if (upperBound == Object.class) {return "?";} else {return "? extends " + typeToString(upperBound);}}private static final long serialVersionUID = 0;}

WildcardType对象中最重要的upper和lower参数,实际上都是由外部对象传入,在Gson的WildcardTypeImpl内部,不过是做了一层适配器。好的,我们先预热到这里,我们进入我们今天的主题Gson的源码。

在我们深入讲Gson源码之前,我们先用一下Gson这个库,领略一下它的魅力。因此我们先构建两个基础的Java模型:

public static class ClassRoom{public String roomName;public int number;public String toString() {return "["+roomName+":"+number+"]";}}public static class User{public String name;public int age;private ClassRoom room;@Overridepublic String toString() {// TODO Auto-generated method stubreturn name+"->"+age+":"+room;}}

模型是用于记录用户信息以及班级信息。为了映射这个对象数据,我们编写一个简单Json字符:

String strJson = "{name:'david',age:19,room:{roomName:'small',number:1}}";
User u = gson.fromJson(strJson, User.class);

最后我们可以将生成的u对象打印一下得到:

david->19:[small:1]

各位看官是否被惊艳到?是的,使用Gson就是可以这么容易的转换Json对象。虽然我们还没开始阅读Gson的源代码,但是我们可以从传入的参数简单看出,在Gson的实现中,一定是大量用到了Java的反射注入技术。我们看下Gson的分包:

gson的分包很简单,从名字上看,每个包分类的目的也都很明确。在Gson中,从普通的Json对象到Gson对象的转换,是通过internal包及其子包bind中的适配器TypeAdapter完成的。而这种完成的类型数据,是依赖于reflect中记录的Type信息来完成的。适配器所需要的输入源或者输出源,是来自于stream包的数据流。当你的对象模型有一些特殊的输出需求或者输入需求,可以通过annotation包中的注解来操纵你的元数据。为了说明这一切,我们回头看一下我们的测试代码,在代码中,我们是直接调用了Gson.fromJson方法。当我们跟踪fromJson这个方法到最后,我们会发现Gson.fromJson方法最终会调用到方法块:

//file:"com/google/gson/Gson.java"@SuppressWarnings("unchecked")public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {JsonReader jsonReader = new JsonReader(json);// step1T object = (T) fromJson(jsonReader, typeOfT);assertFullConsumption(object, jsonReader);return object;}@SuppressWarnings("unchecked")public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {boolean isEmpty = true;boolean oldLenient = reader.isLenient();reader.setLenient(true);try {reader.peek();//step2isEmpty = false;TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);//step3TypeAdapter<T> typeAdapter = getAdapter(typeToken);//step4T object = typeAdapter.read(reader);return object;} catch (EOFException e) {/** For compatibility with JSON 1.5 and earlier, we return null for empty* documents instead of throwing.*/if (isEmpty) {return null;}throw new JsonSyntaxException(e);} catch (IllegalStateException e) {throw new JsonSyntaxException(e);} catch (IOException e) {// TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxExceptionthrow new JsonSyntaxException(e);} finally {reader.setLenient(oldLenient);}}

就像我们上面说的一样,在代码#step1 中,Gson会将真实的字符IO流Reader装饰成为在stream包下的JsonReader流。在代码#step3位置,Gson会通过Java既定的类型找到Gson所转换的type类型(reflect包下的TypeToken对象)。然后通过这个类型调用代码#step4的语句,获取一个类型转换的适配器TypeAdapter。适配器获取到Reader输入源之后,就可以将Json数据转化成为对应的对象。

TypeToken采用一种懒加载的机制来生成TypeToken。这种机制在程序代码中非常常见。

 /*** Gets type literal for the given {@code Type} instance.*/public static TypeToken<?> get(Type type) {return new TypeToken<Object>(type);}@SuppressWarnings("unchecked")protected TypeToken() {this.type = getSuperclassTypeParameter(getClass());this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);this.hashCode = type.hashCode();}/*** Unsafe. Constructs a type literal manually.*/@SuppressWarnings("unchecked")TypeToken(Type type) {this.type = $Gson$Types.canonicalize($Gson$Preconditions.checkNotNull(type));this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type);this.hashCode = this.type.hashCode();}

我们可以看到,TypeToken.get方法,实际上是生成了一个TypeToken对象。而对于TypeToken对象的生成,在TypeToken类中有两种构造方式。第一种无参数的构造方式的作用域设置为protected,意味着你必须要通过继承的方式才能使用它,并且所需要转化的类型需要通过继承里的泛型参数指定。而第二种构造方法需要传入一个Type对象。而这个Type对象就是我们上一篇文章中的Type对象(四种直接子接口和一个实现类)。而Java中的Type类型到Gson中的对象映射,就由$Gson$Type的canonicalize方法完成:

  public static Type canonicalize(Type type) {if (type instanceof Class) {Class<?> c = (Class<?>) type;return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;} else if (type instanceof ParameterizedType) {ParameterizedType p = (ParameterizedType) type;return new ParameterizedTypeImpl(p.getOwnerType(),p.getRawType(), p.getActualTypeArguments());} else if (type instanceof GenericArrayType) {GenericArrayType g = (GenericArrayType) type;return new GenericArrayTypeImpl(g.getGenericComponentType());} else if (type instanceof WildcardType) {WildcardType w = (WildcardType) type;return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());} else {// type is either serializable as-is or unsupportedreturn type;}}

Gson在将数据源传入给适配器Adapter做转换操作的之前,会有一个peek操作。peek跟我们平常用到的流的peek没有什么差异,都是不弹出流数据的情况下查看缓冲区数据。这个peek操作的目的是为了确定以何种输入源类型来处理Json对象。比如,当你输入的JSON串为:

{name:david}

的时候,由于输入员peek出来的是一个"{"字符。在JsonReader对象中,会以"PEEKED_BEGIN_OBJECT" 变量标记peek状态量。而如果你的Json数据是:

[{name:david},{name:Lily}]

的话,由于JsonReader.peek出来的数据是"["字符,因此peek状态量会标记为"PEEKED_BEGIN_ARRAY"。

public JsonToken peek() throws IOException {int p = peeked;if (p == PEEKED_NONE) {p = doPeek();}....default:throw new AssertionError();}}

由于JsonReader我们是刚刚生成,因此peeked状态量的默认值,也就是PEEKED_NONE.这样,程序就跳转到函数doPeek()中。

private int doPeek() throws IOException {int peekStack = stack[stackSize - 1];//状态栈...
}

JsonReader使用栈式的解析,stack存放JsonScope常量所枚举的对象。这个栈中主要存放解析过程中所操纵的对象类型。由于目前Json解析尚未开始,目前栈中存放的是默认值"EMPTY_DOCUMENT"

final class JsonScope {/*** An array with no elements requires no separators or newlines before* it is closed.*/static final int EMPTY_ARRAY = 1;/*** A array with at least one value requires a comma and newline before* the next element.*/static final int NONEMPTY_ARRAY = 2;/*** An object with no name/value pairs requires no separators or newlines* before it is closed.*/static final int EMPTY_OBJECT = 3;/*** An object whose most recent element is a key. The next element must* be a value.*/static final int DANGLING_NAME = 4;/*** An object with at least one name/value pair requires a comma and* newline before the next element.*/static final int NONEMPTY_OBJECT = 5;/*** No object or array has been started.*/static final int EMPTY_DOCUMENT = 6;/*** A document with at an array or object.*/static final int NONEMPTY_DOCUMENT = 7;/*** A document that's been closed and cannot be accessed.*/static final int CLOSED = 8;
}

之后根据读入的第一个字符"{"或者"["返回具体的类型,对于"{"字符,将返回一个"PEEKED_BEGIN_OBJECT"类型。

private void doPeek() {
...
int c = nextNonWhitespace(true);//取得第一个非空白字符switch (c) {...case '[':return peeked = PEEKED_BEGIN_ARRAY;case '{':return peeked = PEEKED_BEGIN_OBJECT;}
...}

但从代码功能上来看,实际上讲Reader.peek代码放在Adapter.read代码前的任何位置都不影响逻辑。我们再继续之前的代码段:

 //com.google.gson.Gson fromJson()1.reader.peek();2.isEmpty = false;3.TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);4.TypeAdapter<T> typeAdapter = getAdapter(typeToken);5.T object = typeAdapter.read(reader);6.return object;

第1行代码,我们通过peek来记录一下Json的最外层对象类型

第3行代码,我们用过传入的类型来生成了一个Gson的类型对象TypeToken

第4行代码,我们通过第三行代码生成的TypeToken对象生成一个数据转换的适配器

第5行代码,我们通过适配器,将输入源中的Json数据转换为Java中的数据对象

上面我们已经说到了第三行代码,TypeToken.get方法调用后,new了一个TypeToken对象。

// code1
@SuppressWarnings("unchecked")public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {TypeAdapter<?> cached = typeTokenCache.get(type);//#1 缓存TypeAdapterif (cached != null) {return (TypeAdapter<T>) cached;}Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();//#2 线程安全boolean requiresThreadLocalCleanup = false;if (threadCalls == null) {threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();calls.set(threadCalls);requiresThreadLocalCleanup = true;}// the key and value type parameters always agreeFutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);if (ongoingCall != null) {return ongoingCall;}try {FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();//#3无用的类装饰threadCalls.put(type, call);for (TypeAdapterFactory factory : factories) {TypeAdapter<T> candidate = factory.create(this, type);//#4查找对应的工厂if (candidate != null) {call.setDelegate(candidate);typeTokenCache.put(type, candidate);return candidate;}}throw new IllegalArgumentException("GSON cannot handle " + type);} finally {threadCalls.remove(type);if (requiresThreadLocalCleanup) {calls.remove();}}}

Gson在获取TypeAdapter的时候,会先从线程的Cache中去取,代码#1很好的诠释了这一点。而为了保证在多线程状态下的状态稳定性,Gson给每个线程都设定了一个Map类型的Cache。#2之后的代码就是在完成这么一项工作。在#3代码里Gson引入了一个新的类FutureTypeAdapter。这个类实际上没有什么实际上的意义,所以可以忽略它。在代码#4的时候,Gson通过遍历自己的factories列表来生成一个TypeAdapter对象。实际上在这一步,Gson在做一个Factory选择,我们来看一个Factory的例子:

// code CollectionTypeAdapterFactory.java
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {Type type = typeToken.getType();Class<? super T> rawType = typeToken.getRawType();if (!Collection.class.isAssignableFrom(rawType)) { #1 集合类判断return null;}...TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);return result;}

这是一个集合类TypeAdapter生成的例子。代码#1就是通过判断传入的对象类型是否是集合类型来进行选择。

(待续)

转载于:https://my.oschina.net/u/874727/blog/749405

轻触开源(二)-Gson项目源码解析_壹相关推荐

  1. 轻触开源(三)-Gson项目源码解析_贰

    2019独角兽企业重金招聘Python工程师标准>>> 转载请注明出处:https://my.oschina.net/u/874727/blog/750473 Q:102525062 ...

  2. SEAL全同态加密开源库(八) rns源码解析(2)

    SEAL全同态加密开源库(七) rns剩余数系统-源码解析 2021SC@SDUSC 2021-11-21 前言 这是SEAL开源库代码分析报告第七篇,本篇将继续分析util文件夹中的rns.h和rn ...

  3. python flask源码解析_用尽洪荒之力学习Flask源码

    [TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...

  4. springboot(二)自动化配置源码解析

    @EnableAutoConfiguration 是开启自动配置的注解,在创建的 SpringBoot 项目中并不能直接看到此注解,它是由组合注解@SpringBootApplication 引入的. ...

  5. android项目源码解析04:新浪微博客户端源码解析

    本文主要介绍如何构建新浪微博客户端.以网上流传weiboSina源码为例介绍,其下载地址为: http://download.csdn.net/detail/ryzhanglu/3453875. 1. ...

  6. BIN,S19,M0T,SREC,HEX文件解析;FileParse(二)之源码解析

    简介 一.摘要 1.描述 2.关键字 二.为何选择C#解析 三.BIN文件解析 四.BIN文件生成 五.S19,M0T,SREC文件解析 六.S19,M0T,SREC文件生成 七.HEX文件解析 八. ...

  7. Android特别的数据结构(二)ArrayMap源码解析

    1. 数据结构 public final class ArrayMap<K,V> implements Map<K,V> 由两个数组组成,一个int[] mHashes用来存放 ...

  8. 解析ViewPager(二)——ViewPager源码解析

    前言 前一篇博客介绍了ViewPager的简单使用,这篇博客主要从源码的角度来解析ViewPager. ViewPager的一些变量 ViewPager是一组视图,那么它的父类必然是ViewGroup ...

  9. Android图片处理二:PhotoView源码解析

    PhotoView 是一个用于处理图片手势的控件,其源码设计很不错,高内聚低耦合,值得我们深入学习下. 1 基本结构 PhotoView 类代码很简单,看下构造就行了. public PhotoVie ...

最新文章

  1. vue后台增删改查_Vue 原生实现商城购物车增删改查
  2. open session and Hibernate事务处理机制
  3. flex将元素放在最后_前端布局——Flex弹性布局
  4. 平板电脑有什么用_除了盖泡面,平板电脑没什么用了
  5. WordPress美化_节日灯笼插件
  6. HTML中布局flex的标签,CSS3---Flex布局--项目属性
  7. python身份证号码共18位_用Python写一个身份证号码校验工具
  8. PHP TP模板下的微博登录(wap)
  9. Beaglebone Black– 智能家居控制系统 LAS - 网页服务器 Node.js 、Web Service、页面 和 TCP 请求转 UDP 发送...
  10. MATLAB数据导入
  11. H5视频自动播放和循环播放
  12. MySQL存储过程中利用do while循环实现将行与行具有层级关系(联系)的行值检索出来
  13. 一个变量命名神器:支持中文转变量名
  14. mysql read rnd next_MySQL rnd_next_编程学问网
  15. MS08067/MS10061漏洞靶机环境搭建总结
  16. SQLite学习之路② Pager模块介绍和Pager对象(2021SC@SDUSC)
  17. 虎牙“维稳”,斗鱼“自救”
  18. 《影响力》读书笔记(一)
  19. linux pkg解压工具,osx – 如何解压缩和打包pkg文件?
  20. 欧洲药典查询-各国药典数据库查询入口

热门文章

  1. 不要忘记监听$destroy事件来清除timer
  2. 有缺憾才有希冀----我的不完美求职经历
  3. 2021年低压电工免费试题及低压电工考试总结
  4. Java虚拟机是如何识别目标方法的?
  5. 数据库之char vchar nchar nvchar的区别
  6. Amazing grace 奇异恩典
  7. boost yield fock介绍
  8. mysql squid_Linux 实现 squid+mysql认证
  9. 基于ARM Cortex-A8和Android 4.x的联动报警系统 (Android 、A8、Linux、驱动、NDK)
  10. 拜占庭将军问题(二)——口头协议