背景
最近在用XStream,轉換XML到Java對象時,發現一個奇怪的現象。Java對象中,在構造函數中取的一些默認值,及在字段定義時賦值的初值,這些值由于有初值,在XML里并沒有映射。結果在XStream反序列化之后,還是null,只有在XML中出現的值才會被賦值。
問題提出
一開始我以為是XStream的能力,它會把XML中沒有的字段找出來,然后設置成null。為了證明,然后我在set方法上設置了斷點,但是發現代碼并沒有進去。我又懷疑是直接反射的變量。然后我又在構造函數里及字段的賦初值的地方設置了斷點,結果發現,依然沒有中斷。
難道,這個對象的構造,沒有調用構造函數?
分析問題
為了搞清楚這個問題,單步一下代碼,從new XStream(new DomDriver());開始。發現當ReflectionProvider reflectionProvider沒有傳入時,會使用JVM::bestReflectionProvider()方法,然后返回了Sun14ReflectionProvider
繼續深入,發現如下代碼:
private Constructor getMungedConstructor(Class type) throws NoSuchMethodException {
final WeakReference ref = (WeakReference)constructorCache.get(type);
Constructor ctor = (Constructor)(ref == null ? null : ref.get());
if (ctor == null) {
ctor = reflectionFactory.newConstructorForSerialization(type, Object.class.getDeclaredConstructor(new Class[0]));
constructorCache.put(type, new WeakReference(ctor));
}
return ctor;
}
// in ReflectionFactory
public Constructor newConstructorForSerialization(Class var1, Constructor var2) {
if(var2.getDeclaringClass() == var1) {
return var2;
} else {
SerializationConstructorAccessorImpl var3 = (new MethodAccessorGenerator()).generateSerializationConstructor(var1, var2.getParameterTypes(), var2.getExceptionTypes(), var2.getModifiers(), var2.getDeclaringClass());
Constructor var4 = this.newConstructor(var2.getDeclaringClass(), var2.getParameterTypes(), var2.getExceptionTypes(), var2.getModifiers(), langReflectAccess().getConstructorSlot(var2), langReflectAccess().getConstructorSignature(var2), langReflectAccess().getConstructorAnnotations(var2), langReflectAccess().getConstructorParameterAnnotations(var2));
this.setConstructorAccessor(var4, var3);
return var4;
}
}
解析
- 反序列化使用
Sun14ReflectionProvider::getMungedConstructor()
方法取構造函數。 - 在這里
reflectionFactory.newConstructorForSerialization(type, Object.class.getDeclaredConstructor(new Class[0]));
傳入了目標類型,及Object類型的默認構造函數。 - 然后在
Sun14ReflectionProvider::newConstructorForSerialization()
中用Object類型的構造函數,產生了目標類型的構造函數。
在函數在sun的API中已經提供,Sun14ReflectionProvider::newConstructorForSerialization()
分析:
-
(new MethodAccessorGenerator()).generateSerializationConstructor
返回了SerializationConstructorAccessorImpl對象,用于Object構造函數可以訪問目標類型。 -
this.newConstructor
來新產生Object類型的構造函數。此函數是新生產出來的,和在函數區已有的構造函數不是同一個。 -
this.setConstructorAccessor(var4, var3);
最后把兩者關聯。
最后:幾種ReflectionProvider
public synchronized ReflectionProvider bestReflectionProvider() {
if (reflectionProvider == null) {
try {
if ( canUseSun14ReflectionProvider() ) {
String cls = "com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider";
reflectionProvider = (ReflectionProvider) loadClass(cls).newInstance();
} else if (canUseHarmonyReflectionProvider()) { // call isHarmony()
String cls = "com.thoughtworks.xstream.converters.reflection.HarmonyReflectionProvider";
reflectionProvider = (ReflectionProvider) loadClass(cls).newInstance();
}
if (reflectionProvider == null) {
reflectionProvider = new PureJavaReflectionProvider();
}
} //...
}
return reflectionProvider;
}
private boolean canUseSun14ReflectionProvider() {
return (isSun()
|| isApple()
|| isHPUX()
|| isIBM()
|| isBlackdown()
|| isBEAWithUnsafeSupport()
|| isHitachi()
|| isSAP()
|| isDiablo())
&& is14()
&& loadClass("sun.misc.Unsafe") != null;
}
private static boolean isHarmony() {
return vendor.indexOf("Apache Software Foundation") != -1;
}
- Sun14ReflectionProvider是最優的的反序列化器。
- Sun14ReflectionProvider反序列化器,Sun, Apple, IBM等各大JVM都有實現,并且版本高于JDK1.4。
- Sun14ReflectionProvider反序列化器內部使用的用是sun提供的反射API。
- Harmony是Apache Harmony項目,一個開源的JVM虛擬機。
- 使用XStream時改為
new XStream(new PureJavaReflectionProvider(), new DomDriver());
,可以解決此問題,使用類的正常構造函數。
結論
- 類在構造時,可以避開自己的構造函數,而合用Object的構造函數。
- 類字段的初始值的賦值,在編譯時會編譯到其構造函數中。