问题 readResolve无法正常工作? :出现了一个Guava的SerializedForm实例


在我们的一个数据结构的反序列化期间(使用默认机制(没有自定义writeObject / readObject)),显示了一个ImmutableMap $ SerializedForm实例(来自谷歌的Guava库)。

这样的实例不应该从guava的客户端看到,因为SerializedForm的实例被替换为readResolve(参见com.google.common.collect.Immutable类中的“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)中有自定义序列化代码。

更新,这是堆栈跟踪的顶部:

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)

10683
2018-02-02 10:25


起源

你能展示完整的堆栈跟踪吗? - axtavt


答案:


本周,我们又面临这个错误;但我找到了根本原因。 ObjectInputStream使用的类加载器是高度依赖于上下文的(有些人会说 不确定的)。以下是Sun文档的相关部分(它是ObjectInputStream#resolveClass(ObjectStreamClass)的摘录):

[类加载器]确定如下:如果当前线程的堆栈上有一个方法,其声明类是由用户定义的类加载器定义的(并且不是为实现反射调用而生成的),那么它就是类加载器对应于与当前执行帧最接近的这种方法;否则,它为空。如果此调用导致ClassNotFoundException并且传递的ObjectStreamClass实例的名称是基本类型或void的Java语言关键字,则将返回表示该基本类型或void的Class对象(例如,名为“int”的ObjectStreamClass) “将被解析为Integer.TYPE)。否则,将向此方法的调用者抛出ClassNotFoundException。

在我们的应用程序中,我们有一个Eclipse插件B依赖于一个实用程序专用插件A.我们反序列化其类在B中的对象,但反序列化是在A中启动的(在那里创建一个ObjectInputStream),这就是问题所在。很少(即取决于文档所说的调用堆栈)反序列化选择了错误的类加载器(一个无法加载B类的加载器)。为了解决这个问题,我们将一个适当的加载器从顶级反序列化调用程序(在B中)传递给A中的实用程序方法。此方法现在使用自定义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);
                }
            };

7
2018-02-22 08:16



我也在OSGi环境中看到了这一点。我有一个捆绑负责数据库层的[de]序列化,我无法控制如何使用类加载器。因此,我必须设置DynamicImport-Package:*以确保可以看到所有类。不幸的是,我忘了从包含要反序列化的类的包中导出包,而且忘记在这个类中实现序列化代理。我运行了一个调试器,我也发现了ClassNotFoundException。惭愧这是没有报道! - Dan Gravell


请提交一个错误: http://code.google.com/p/guava-libraries/issues/entry

如果您可以为您附加一个触发此错误的独立程序,那将有所帮助!


3
2018-02-02 15:09



我无法在小型独立程序上重现该错误。 - Clément Hurlin


我们找到了如何避免这个bug,但没有找到导致它的原因。

当我们反序列化ArrayListMultiMap的一个实例时,类加载器找不到我们的类(com.blah ....),因为使用了Guava的类加载器(在从ObjectInputStream#resolveClass调用的代码中)而不是默认的类加载器。然后,ObjectInputStream通过使用ClassCastExceptions填充其HandleList#条目的实例来传播失败。这样的异常最终会导致跳过readResolve,这就解释了为什么会出现ImmutableMap $ SerializedForm。

奇怪的是,我们序列化和反序列化了许多其他数据结构(包括我们自己的和番石榴)。自我序列化guava的ArrayListMultimap(使用自定义writeObject)避免了这个错误(即使我们序列化了Guava集合的实例(尽管不是Multimaps))。

我们不明白为什么类加载器突然出错,但一个bug必须潜伏在某个地方。我相信我们收到了ClassCastException而不是ClassNotFoundException,因为ObjectInputStream中的错误处理是错误的(即使缺少某些类,也不应该跳过readResolve)。


3
2018-02-03 08:31





问题是writeReplace()/ readResolve()与对象图中的循环引用不能很好地匹配。 writeReplace()和readResolve()是不对称的。在序列化期间,Java将替换所有引用,包括循环引用。但在反序列化期间,Java不会解析循环引用。不幸的是,这是设计上的。从 序列化规范

注 - 在对象上不调用readResolve方法   对象是完全构造的,因此对它的任何引用都是对象   对象图不会更新为由指定的新对象   的readResolve。但是,在一个对象的序列化过程中   writeReplace方法,所有对原始对象的引用   替换对象的对象图被替换为对的引用   替换对象。因此在物体存在的情况下   serialized指定一个替换对象,其对象图有一个   引用原始对象,反序列化将导致   不正确的对象图。此外,如果参考类型的   正在读取的对象(由writeReplace提名)和原始对象   不兼容,对象图的构造将提出一个   ClassCastException异常。

Guava开发人员可以通过使ImmutableMap $ SerializedForm扩展ImmutableMap并委托给正确的ImmutableMap实例来解决这个问题。当发生循环引用时,调用者将获得SerializedForm而不是直接引用ImmutableMap,但它比ClassCastException更好。


2
2017-09-06 00:17





有同样的问题。原来,不可变列表的成员对象的类不在反序列化方面的类路径上。 但是这个事实隐藏在ClassCastException之后。

现在我用这个结构做了更好的错误检测:

final ImmutableSet.Builder<Object> notFoundClasses = ImmutableSet.builder();
    try {
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream) {
            @Override
            protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
                try {
                    return super.resolveClass(desc);
                } catch (ClassNotFoundException e) {
                    notFoundClasses.add(desc.getName());
                    throw e;
                }
            }
        };
        return (T) objectInputStream.readObject();
    } catch (ClassCastException e) {
        throw Exceptions.runtime(e, "ClassCastException while de-serializing '%s', classes not found are: %s", objectClass, notFoundClasses.build());
    } catch (IOException | ClassNotFoundException e) {
        throw Exceptions.runtime(e, "Could not de-serialize '%s'", objectClass);
    }

1
2018-03-30 12:54