前言
最近做项目遇到了一个很奇怪的问题,情况如下:
创建对象TestBean,其中type和name需要接口返回并解析,time字段需要客户端修改,做一些必要的记录,希望time的默认值为10:
val jsonStr ="{type: 99, name:\"superman\"}"
data class TestBean(val type: Int, val name: String, var time: Long = 10)
在运行前,我认为这段代码非常完美,但是结果却很意外:
难道Gson把构造方法中的time设置成0了吗?,再次修改代码:
data class TestBean(val type: Int, val name: String) {
var time: Long = 10
override fun toString(): String {
return "type:$type, name:$name, time:$time"
}
}
默认情况下,data class的toString方法只会打印构造方法中的属性,所以还需要重写一下toString。我把time属性从构造方法中移出,这次应该是稳得一匹了吧:
what???,time的值并不是10,虽然没有找到原因,但是我还可以再改,男人不可以说不行:
data class TestBean(val type: Int, val name: String) {
var time: Long = 10
init {
time = 10
Log.e("lzp", "TestBean create")
}
override fun toString(): String {
return "type:$type, name:$name, time:$time"
}
}
这一次,我在init方法中,手动设置time=10,并且输出日志,对象创建时一定会执行init方法,绝对ojbk:
init方法也没执行???好的,让我静下心来仔细的找到问题到底出现在哪里。
正文
经过刚才的踩坑,可以肯定的是,Gson没有调用TestBean的构造方法。那么他是怎么创建TestBean的呢?一般来说创建对象有以下几种方式:
调用构造方法:TestBean() (new关键字或者反射最终都是使用了构造方法)
复制:Object.copy()
还是要从Gson的源码去分析这个问题:
val testBean = Gson().fromJson(jsonStr, TestBean::class.java)
我们对fromJson方法进行追踪,发现fromJson重写了好几个,最终会定位到:
public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true);
try {
reader.peek();
isEmpty = false;
TypeToken typeToken = (TypeToken) TypeToken.get(typeOfT);
// 获取具体解析的TypeAdapter
TypeAdapter typeAdapter = getAdapter(typeToken);
// 返回解析的结果
T object = typeAdapter.read(reader);
return object;
} catch (EOFException e) {
...
}
}
由于TypeAdapter的子类实在是太多了,如果要去仔细的翻源码太耗时间,直接断点:
我们要找的就是ReflectiveTypeAdapterFactory的内部类Adapter,直接找他的read方法:
@Override public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
// 这里创建对象了
T instance = constructor.construct();
// 解析对应的属性,忽略
try {
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
field.read(in, instance);
}
}
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
in.endObject();
return instance;
}
终于找到创建对象的代码,让我们看看这个constructor到底是个什么玩意,经过各种定位,最终我们找到了分析问题的最关键类:ConstructorConstructor。
public ObjectConstructor get(TypeToken typeToken) {
final Type type = typeToken.getType();
final Class super T> rawType = typeToken.getRawType();
// 优先使用Type获取对象的构造方法
final InstanceCreator typeCreator = (InstanceCreator) instanceCreators.get(type);
if (typeCreator != null) {
return new ObjectConstructor() {
@Override public T construct() {
return typeCreator.createInstance(type);
}
};
}
// 再次使用rawType获取对象的构造方法
@SuppressWarnings("unchecked") // types must agree
final InstanceCreator rawTypeCreator =
(InstanceCreator) instanceCreators.get(rawType);
if (rawTypeCreator != null) {
return new ObjectConstructor() {
@Override public T construct() {
return rawTypeCreator.createInstance(type);
}
};
}
/*** 分割线以上都是instanceCreators取出来的**/
// 通过默认的无参构造方法,请注意是无参的构造方法
ObjectConstructor defaultConstructor = newDefaultConstructor(rawType);
if (defaultConstructor != null) {
return defaultConstructor;
}
// 以上三步仍没有找到构造方法时的处理
ObjectConstructor defaultImplementation = newDefaultImplementationConstructor(type, rawType);
if (defaultImplementation != null) {
return defaultImplementation;
}
// 通过不安全的形式创建对象???
return newUnsafeAllocator(type, rawType);
}
其中前两中方法都是我们通过配置Gson可以设置的,因为我们并没有设置,所以这里先跳过,我们需要关注后三步,第一步:找到无参的构造方法:
private ObjectConstructor newDefaultConstructor(Class super T> rawType) {
try {
// 反射无参的构造方法
final Constructor super T> constructor = rawType.getDeclaredConstructor();
if (!constructor.isAccessible()) {
accessor.makeAccessible(constructor);
}
return new ObjectConstructor() {
@Override public T construct() {
try {
Object[] args = null;
// 调用无参构造方法创建实例
return (T) constructor.newInstance(args);
} catch (Exception e) {
...
}
}
};
// 没有无参构造方法直接返回null
} catch (NoSuchMethodException e) {
return null;
}
}
熟悉反射的话,这里都很好理解,如果还不太熟悉反射的话,可以先去查看一下反射的知识。如果没有无参的构造方法,会进入newDefaultImplementationConstructor:
private ObjectConstructor newDefaultImplementationConstructor(
final Type type, Class super T> rawType) {
if (Collection.class.isAssignableFrom(rawType)) {
... 集合子类会有一些创建操作
}
if (Map.class.isAssignableFrom(rawType)) {
... Map子类的创建操作
}
return null;
}
newDefaultImplementationConstructor只对集合和Map的子类有创建操作,很明显TestBean只是一个普通对象,并不符合需求。经过以上四步,我们没有找到任何可以创建TestBean的方法,那么唯一的答案就只能是在newUnsafeAllocator(type, rawType)中了:
public static UnsafeAllocator create() {
// 最关键
try {
// 反射找到sun.misc.Unsafe类
Class> unsafeClass = Class.forName("sun.misc.Unsafe");
// 找到sun.misc.Unsafe类中的theUnsafe属性
Field f = unsafeClass.getDeclaredField("theUnsafe");
// 激活theUnsafe属性
f.setAccessible(true);
// 得到theUnsafe的对象
final Object unsafe = f.get(null);
// 反射allocateInstance方法
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
return new UnsafeAllocator() {
@Override
@SuppressWarnings("unchecked")
public T newInstance(Class c) throws Exception {
assertInstantiable(c);
// 调用allocateInstance方法,创建类型为c的对象
return (T) allocateInstance.invoke(unsafe, c);
}
};
} catch (Exception ignored) {
}
// 第二步的实现方案和第一步其实一样,所以忽略
...
// 第三步,在我的版本源码中ObjectInputStream找不到newInstance方法,所以忽略
...
}
还是反射相关的调用,理解起来并不难,重点是理解sun.misc.Unsafe类到底是个什么东西,可以无视对象的构造方法,创建新的对象,我从网上分享的源码中,截取一部分注释的描述一下它:
sun.misc.Unsafesh收集了很多底层的不安全的操作方法。尽管类和方法是开放的,但是要谨慎的使用它,因为只有信任的代码才可以使用它。
allocateInstance方法:创建一个实例,但是不运行它的构造方法,请手动初始化。
sun.misc.Unsafe还有很多其他的native方法,Google已经明确说明要谨慎使用,除非是极其特殊的情况,我们还是把它记在心里就好了。
总结
现在我们已经了解了Gson创建对象的过程,那么一开始的问题要怎么解决呢?经过分析源码我们有以下两种方案:
第一种方案::Gson配置TypeAdapter。
val testBean2 = GsonBuilder().registerTypeAdapter(
TestBean::class.java, TestBeanTypeAdapter()
).create().fromJson(jsonStr, TestBean::class.java)
class TestBeanTypeAdapter : JsonSerializer,
JsonDeserializer {
@Throws(JsonParseException::class)
override fun deserialize(
json: JsonElement?, typeOfT: Type?,
context: JsonDeserializationContext?
): TestBean? {
return if (json == null) {
null
} else {
if (json is JsonObject) {
return TestBean(json.get("type").asInt, json.get("name").asString)
} else {
return null
}
}
}
override fun serialize(
src: TestBean?, typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement? {
return JsonPrimitive(Gson().toJson(src))
}
}
第二种方案:为TypeAdapter,设置无参的构造方法:
class TestBean {
var type: Int = 0
var name: String = ""
var time: Long = 10
init {
Log.e("lzp", "TestBean create")
}
constructor(type: Int, name: String){
this.type = type
this.name = name
}
override fun toString(): String {
return "type:$type, name:$name, time:$time"
}
}