故事從一個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
,在面對空安全屬性上,各位同窗仍是須要多留個心眼,防範這些「非法移民」。