使用readClassDescriptor()和resolveClass()来允许序列化版本控制

orb*_*ish 5 java version-control serialization version deserialization

我正在研究Java序列化机制中的不同选项,以允许我们的类结构灵活地进行版本容忍存储(并提倡不同的机制,您不需要告诉我).

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

但是,重命名一个类或将它移动到另一个包中已经证明要困难得多.我在这个问题中发现我能够通过继承ObjectInputStream并覆盖readClassDescriptor()来做一个简单的类重命名和/或移动包:

    if (resultClassDescriptor.getName().equals("package.OldClass"))
        resultClassDescriptor = ObjectStreamClass.lookup(newpackage.NewClass.class);
Run Code Online (Sandbox Code Playgroud)

对于简单的重命名,这很好.但是,如果您尝试添加或删除字段,则会收到java.io.StreamCorruptedException.更糟糕的是,即使添加或删除了某个字段,然后重命名该类,也会发生这种情况,这可能会导致多个开发人员或多个签名出现问题.

基于我已经完成的一些阅读,我尝试了一下同样重写了resolveClass(),并且我们正确地将名称重新命名为新类,但没有加载旧类本身并轰炸字段更改.但这来自于对序列化机制的一些细节的非常模糊的理解,我不确定我是否甚至咆哮着正确的树.

所以2个确切的问题:

  1. 为什么使用readClassDescriptor()重新创建类名,导致反序列化在正常的兼容类更改时失败?
  2. 有没有办法使用resolveClass()或其他机制来解决这个问题并允许类进化(添加和删除字段)并重命名/重新打包?

我四处寻找,无法在SO上找到一个同等的问题.无论如何,请指出这样一个问题,如果它存在,但请仔细阅读这个问题,除非另一个问题实际上回答了我的确切问题,否则你不要关闭我.

gap*_*nov 8

我和你一样有灵活性问题我找到了方法.所以这里是我的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;
    }
Run Code Online (Sandbox Code Playgroud)