使用readClassDescriptor(),也许resolveClass(),以允许序列化版本(

2019-06-25 07:13发布

我调查了Java序列化机制不同的选项,允许版本容错存储在我们的阶级结构的灵活性(和倡导不同的机制,你不需要告诉我)。

例如,默认的序列化机制可以处理添加和删除字段,如果只需要向后兼容性。

重命名类或者将其移动到不同的封装已证明是困难得多,虽然。 我发现这个问题 ,我能够做一个简单的类重命名和/或移动包,通过继承ObjectInputStream的和压倒一切的readClassDescriptor():

    if (resultClassDescriptor.getName().equals("package.OldClass"))
        resultClassDescriptor = ObjectStreamClass.lookup(newpackage.NewClass.class);

这是对简单的重命名。 但是,如果你再尝试添加或删除字段,你会得到一个java.io.StreamCorruptedException。 更糟的是,这种情况即使现场已经被添加或删除, 然后重命名类,这可能会导致与多个开发商或多个签入的问题。

基于一些阅读我做了,我尝试了一下用也覆盖resolveClass(),与我们正确repointing名称到新类的想法,但不加载在球场上改变旧的类本身和轰炸。 但是,这来自于序列化机制的一些细节非常模糊的认识,我不知道如果我甚至吠叫右树。

因此,2个精确的问题:

  1. 为什么repointing使用readClassDescriptor()导致反序列化失败正常,兼容的类变化的类名?
  2. 是否有使用resolveClass()或其他机制来解决这个问题,并允许类两种演变方式(添加和删除字段),并改名/重新包装?

我戳四周,找不到对SO等效问题。 通过一切手段,指出我这样一个问题,如果它存在,但请阅读问题不够仔细,除非另外一个问题居然回答我确切的问题,你不收我。

Answer 1:

我跟你一样的灵活性同样的问题,我发现了道路。 所以在这里我的版本readClassDescriptor的()

    static class HackedObjectInputStream extends ObjectInputStream
{

    /**
     * Migration table. Holds old to new classes representation.
     */
    private static final Map<String, Class<?>> MIGRATION_MAP = new HashMap<String, Class<?>>();

    static
    {
        MIGRATION_MAP.put("DBOBHandler", com.foo.valueobjects.BoardHandler.class);
        MIGRATION_MAP.put("DBEndHandler", com.foo.valueobjects.EndHandler.class);
        MIGRATION_MAP.put("DBStartHandler", com.foo.valueobjects.StartHandler.class);
    }

    /**
     * Constructor.
     * @param stream input stream
     * @throws IOException if io error
     */
    public HackedObjectInputStream(final InputStream stream) throws IOException
    {
        super(stream);
    }

    @Override
    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException
    {
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();

        for (final String oldName : MIGRATION_MAP.keySet())
        {
            if (resultClassDescriptor.getName().equals(oldName))
            {
                String replacement = MIGRATION_MAP.get(oldName).getName();

                try
                {
                    Field f = resultClassDescriptor.getClass().getDeclaredField("name");
                    f.setAccessible(true);
                    f.set(resultClassDescriptor, replacement);
                }
                catch (Exception e)
                {
                    LOGGER.severe("Error while replacing class name." + e.getMessage());
                }

            }
        }

        return resultClassDescriptor;
    }


Answer 2:

问题是,readClassDescriptor应该告诉的ObjectInputStream如何阅读这是目前您正在阅读的流中的数据。 如果你看一个串行数据流里面,你会看到,它不仅存储数据,但很多关于元数据的到底是什么领域都存在。 这是什么让序列化处理简单的现场添加/删除。 然而,当你重写方法和丢弃从流返回的信息,你放弃什么字段是序列化的数据信息。

认为 ,解决问题的办法是采取)的super.readClassDescriptor(返回的值,并创建一个新的类描述符返回新类的名称,否则返回从旧的描述符中的信息。 (虽然在看着ObjectStreamField进行,它可能会比这更复杂,但这是一般的想法)。



Answer 3:

这是writeReplace()和的readResolve()是。 你正在做的要复杂得多比它确实是。 请注意,您可以在相关的两个物体或在你的子类的对象流类定义了这些方法。



Answer 4:

我还没有与类描述修修补补多就够了,但如果你的问题是只是重命名和重新包装有应该是一个更容易的解决方案。 你可以只是简单的用文本编辑器编辑您的序列化的数据文件,只是更换新的旧的名称。 它的存在在人类可读的形式。 例如假设我们有此OldClass置于内部oldpackage和含有oldField ,如下所示:

package oldpackage;

import java.io.Serializable;

public class OldClass implements Serializable
{
    int oldField;
}

现在,当我们这个序列化类的实例,并得到这样的:

¬í sr oldpackage.OldClasstqŽÇ§Üï I oldFieldxp    

现在,如果我们想改变类的名字NewClass并把里面的newpackage并更改其字段的名称,以newField ,我只是将其重命名文件,如下所示:

¬í sr newpackage.NewClasstqŽÇ§Üï I newFieldxp    

并确定适当的serialVersionUID为新类。

就这样。 没有扩展和压倒一切的需要。



文章来源: Using readClassDescriptor() and maybe resolveClass() to permit Serialization versioning