readResolve无法正常工作?:Guava的SerializedForm的一个实例出现
问题内容:
在对我们的一种数据结构进行反序列化时(使用默认机制(无自定义writeObject / readObject)),显示了ImmutableMap $
SerializedForm的实例(来自Google的Guava库)。
guava的客户端不应该看到这样的实例,因为SerialreadForm的实例已使用readResolve替换(例如,参见com.google.common.collect.ImmutableMap类中的“
writeReplace”)。
因此,反序列化失败并显示以下消息:
java.lang.ClassCastException: cannot assign instance of com.google.common.collect.ImmutableMap$SerializedForm
to field .. of type java.util.Map in instance of com.blah.C
这是正确的,因为ImmutableMap $ SerializedForm不是java.util.Map的子类型,但是应该将其替换。怎么了?
我们在com.blah.C类中没有自定义的writeObject /
readObject。我们在父对象(包含com.blah.C)中确实有自定义序列化代码。
更新,这是stacktrace的顶部:
java.lang.ClassCastException: cannot assign instance of com.google.common.collect.ImmutableSet$SerializedForm to field com.blah.ast.Automaton.bodyNodes of type java.util.Set in instance of com.blah.ast.Automaton
at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2039)
at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1212)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1952)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1870)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
at java.util.ArrayList.readObject(ArrayList.java:593)
at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1946)
at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:479)
at com.blah.ast.AstNode.readObject(AstNode.java:189)
at sun.reflect.GeneratedMethodAccessor10.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
at java.util.ArrayList.readObject(ArrayList.java:593)
at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1946)
at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:479)
at com.blah.ast.AstNode.readObject(AstNode.java:189)
问题答案:
这周,我们再次遇到了这个错误。但我找到了根本原因。ObjectInputStream使用的类加载器是高度依赖上下文的(有人会说不 确定性
)。这是Sun文档的相关部分(摘自ObjectInputStream#resolveClass(ObjectStreamClass)):
[class loader]的确定如下:如果当前线程的堆栈上有一个方法的声明类是由用户定义的class
loader定义的(并且不是为实现反射调用而生成的),则它是class
loader对应于最接近当前执行帧的这种方法;否则为null。如果此调用导致ClassNotFoundException且传递的ObjectStreamClass实例的名称是原始类型或void的Java语言关键字,则将返回表示该原始类型或void的Class对象(例如,名称为“
int”的ObjectStreamClass
”将解析为Integer.TYPE)。否则,ClassNotFoundException将被抛出给该方法的调用者。
在我们的应用程序中,我们有一个Eclipse插件B,它依赖于仅实用程序的插件A。我们正在反序列化其类在B中的对象,但是反序列化是在A中启动的(在那里创建ObjectInputStream),这就是问题所在。反序列化很少(即取决于文档所说的调用堆栈)选择了错误的类加载器(一个不能加载B类的加载器)。为了解决此问题,我们将适当的加载程序从顶级反序列化调用程序(在B中)传递给A中的Utility方法。此方法现在使用如下的自定义ObjectInputStream(请注意,自由变量“
loader”):
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)) {
@SuppressWarnings("rawtypes")
@Override
protected Class resolveClass(ObjectStreamClass objectStreamClass)
throws IOException, ClassNotFoundException {
return Class.forName(objectStreamClass.getName(), true, loader);
}
};