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

怎甘沉沦 提交于 2019-12-05 19:45:56

上篇文章<轻触开源-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;
		@Override
		public String toString() {
			// TODO Auto-generated method stub
			return 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);// step1
    T 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();//step2
      isEmpty = false;
      TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);//step3
      TypeAdapter<T> typeAdapter = getAdapter(typeToken);//step4
      T 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 JsonSyntaxException
      throw 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 unsupported
      return 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 缓存TypeAdapter
    if (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 agree
    FutureTypeAdapter<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就是通过判断传入的对象类型是否是集合类型来进行选择。

(待续)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!