故事从一个Koltin
项目新功能的调试过程提及,应用抛出了一个异常,堆栈信息(局部)以下:java
[http-nio-8080-exec-2] ERROR c.c.p.f.i.c.c.e.GlobalExceptionHandler - java.lang.IllegalArgumentException: Parameter specified as non-null is null: method ${className}.<init>, parameter ${fieldName}
复制代码
是一个常见的空安全异常,向空安全的变量赋了Null
值。但值变量是也是来自空安全变量,为何会出现这样的状况呢?接下来逐步分析。安全
Kotlin
的在类成员变量的空安全是在编译级别实现的,即在编译成class
文件的时候在类的构造函数添加了空值检查bash
// 第一个参数是构造函数传入的变量值,第二个参数是变量名
Intrinsics.checkParameterIsNotNull(name, "name");
复制代码
这个检查方法会在传入Null
值时抛出java.lang.IllegalArgumentException
异常,以此保证类的成员属性是空安全。ide
Gson
的反序列化的主流程逻辑集中在ReflectiveTypeAdapterFactory.BoundField.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;
}
复制代码
流程十分简单:ui
Json
的输入流,为空则返回Null
instance
Json
字符串给instance
属性赋值最重要的是第二步,即对象的实例化过程constructor.construct()
。constructor
是一个什么对象呢?spa
constructor
是一个封装了对象构造方法的对象,它的生成逻辑以下:调试
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
final Type type = typeToken.getType();
final Class<? super T> rawType = typeToken.getRawType();
// first try an instance creator
@SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
if (typeCreator != null) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return typeCreator.createInstance(type);
}
};
}
// Next try raw type match for instance creators
@SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> rawTypeCreator =
(InstanceCreator<T>) instanceCreators.get(rawType);
if (rawTypeCreator != null) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return rawTypeCreator.createInstance(type);
}
};
}
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
if (defaultConstructor != null) {
return defaultConstructor;
}
ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
if (defaultImplementation != null) {
return defaultImplementation;
}
// finally try unsafe
return newUnsafeAllocator(type, rawType);
}
复制代码
InstanceCreator
,则返回注册的 InstanceCreator
InstanceCreator
InstanceCreator
UnsafeAllocator
此次异常的分支条件调用的是UnsafeAllocator
,其源码就不进行具体解析,其工做原理就是包装了sun.misc.Unsafe
的方法来完成对象的实例化,这个sun.misc.Unsafe
就是此次异常的“病根”。code
在Java/Kotlin
中,对象的建立方式比较常见的是如下几种:对象
new
语句, 好比 MyClass demo = new MyClass()
Class
对象的newInstance()
方法,好比MyClass demo = MyClass.class.newInstance()
(前提就是必须提供无参的构造函数)Constructor
对象来建立对象方式虽多,但异曲同工,在JVM层面,其对应都是三条重要指令,以java.lang.StringBuilder
为例
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
复制代码
其对应的逻辑分表别是
<init>
方法能够看到对象的建立过程不是原子性的,因此还存在一种特殊途径来建立对象,即sun.misc.Unsafe
类。
它建立对象的方法和放射相似,但它不会执行INVOKESPECIAL
指令,建立的对象的全部成员变量都处于空值状态。
那么回到应用的异常状况,在运行时Gson
经过反序列化生成了一个与类型声明不符的“半成品”对象,这个异常值的传递至一个空安全字段时,就受到了Koltin
的检查致使异常发生。
Kotlin
的空安全确实给开发人员带了极大的便利,但也带来了隐患,便可能会给开发人员带来虚假的安全感:Kotlin
终究仍是在基于JVM
的静态语言,面对相似sun.misc.Unsafe
等相似的底层操做,空检查能够被轻易突破,且这种非法对象的存在是浑然不知的,可是却给咱们的应用带来真切的Bug
,在面对空安全属性上,各位同窗仍是须要多留个心眼,防范这些“非法移民”。