【问题标题】:Using readClassDescriptor() and maybe resolveClass() to permit Serialization versioning使用 readClassDescriptor() 和 resolveClass() 来允许序列化版本控制
【发布时间】:2012-06-11 18:59:55
【问题描述】:

我正在研究 Java 序列化机制中的不同选项,以便在我们的类结构中实现版本容错存储的灵活性(并且提倡使用不同的机制,您无需告诉我)。

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

但事实证明,重命名一个类或将其移动到不同的包要困难得多。我在this question 中发现,通过继承 ObjectInputStream 并覆盖 readClassDescriptor(),我能够进行简单的类重命名和/或移动包:

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

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

根据我所做的一些阅读,我尝试了一些覆盖resolveClass()的方法,我们的想法是我们正确地将名称重新指向新类,但不加载旧类本身并轰炸字段更改.但这来自对序列化机制的一些细节的非常模糊的理解,我什至不确定我是否在吠叫正确的树。

所以 2 个精确的问题:

  1. 为什么使用 readClassDescriptor() 重新指向类名会导致 反序列化在正常、兼容的类更改上失败?
  2. 有没有办法使用 resolveClass() 或其他机制来绕过 这并允许类进化(添加和删除字段)并成为 重命名/重新包装?

我四处寻找,找不到关于 SO 的等效问题。无论如何,如果存在这样的问题,请向我指出,但请仔细阅读该问题,除非另一个问题真正回答了我的确切问题,否则您不会关闭我。

【问题讨论】:

  • 你找到解决办法了吗?
  • @orbfish 如果你发现了,请分享你的解决方案
  • @enthu-man 不知何故我错过了关闭它,已经很长时间了,我不再有问题的代码。这里有3个好看的解决方案,我会尝试,如果你找到一个可行的,我会接受它;)

标签: java serialization version-control version deserialization


【解决方案1】:

问题在于 readClassDescriptor 应该告诉 ObjectInputStream 如何读取您正在读取的流中当前的数据。如果你查看一个序列化的数据流,你会发现它不仅存储数据,而且还有很多关于存在哪些字段的元数据。这就是允许序列化处理简单字段添加/删除的原因。但是,当您覆盖该方法并丢弃从流返回的信息时,您将丢弃有关序列化数据中的字段的信息。

我 认为 问题的解决方案是获取 super.readClassDescriptor() 返回的值并创建一个返回新类名的 new 类描述符,但否则返回旧描述符中的信息。 (虽然,在查看 ObjectStreamField 时,它可能比这更复杂,但这是一般的想法。

【讨论】:

    【解决方案2】:

    我对类描述符的修改还不够多,但是如果您的问题只是重命名和重新打包,那么有一个更简单的解决方案。您可以简单地使用文本编辑器编辑序列化数据文件,然后将旧名称替换为新名称。它以人类可读的形式存在。例如,假设我们将这个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。

    就是这样。无需扩展和覆盖。

    【讨论】:

    • 序列化数据是二进制的。您可能会使用文本编辑器损坏它。不是一个实际的答案。
    • 我已经测试过了。你可以试试看。
    • 你到底测试了什么?用哪个文本编辑器?在什么系统上?用什么序列化数据?包含什么二进制数据?
    • 1- 重命名或重新打包类或重命名其字段。 2- Notepad++ 5.9.6.2 3- Windows 7 4&5- 我按照我描述的方式做了上面的例子。我已经在我的几个项目中尝试过这个。从来没有问题。你测试的时候有问题吗?
    【解决方案3】:

    这就是 writeReplace() 和 readResolve() 的用途。你让它变得比实际复杂得多。请注意,您可以在相关的两个对象中或在这些对象流类的子类中定义这些方法。

    【讨论】:

    • 我不认为这样做,但正如我上面所说,我不再有代码来测试。如果我再次看到问题,我会试试这个,谢谢!
    【解决方案4】:

    我和你一样在灵活性方面遇到了同样的问题,我找到了方法。 所以这里是我的 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;
        }
    

    【讨论】:

      【解决方案5】:

      我最近遇到了同样的问题,即 StreamCorruptedException 反序列化从一个包移动到另一个包的类的对象,然后通过添加新字段以兼容的方式演变。虽然@gaponov answer 最初解决了它,但我发现以下解决方案更合适,因为它不需要弄乱类名。使用 ObjectInputStreamAdapter 的类定义了映射,内部类 ObjectInputStreamAdapter 只重新定义了 resolveClass 方法:

          public class Deserializer {
      
          /*
           * Mapping that stores the specific new classes to use for old serialized
           * class names in order to transform old classes to the new ones for
           * compatibility reasons
           */
          private static final Map<String, Class<?>> classMapping = new HashMap<>();
      
          static {
              classMapping.put("com.example.old.SomeClass",
                      SomeClass.class);
              classMapping.put("com.example.old.SomeClass2",
                      SomeClass2.class);
          }
      
          public void deserialize(byte[] bytes) {
              try (ObjectInputStream o =
                      new ObjectInputStreamAdapter(new ByteArrayInputStream(bytes))) {
                  Object object = o.readObject();
                  /* ... */
              } catch (Exception e) {
                  throw new SerializationException("Cannot deserialize", e);
              }
          }
      
          /*
           * Adaptor that transform old classes to the new classes for compatibility
           * reasons
           */
          private class ObjectInputStreamAdapter extends ObjectInputStream {
      
              public ObjectInputStreamAdapter(InputStream in) throws IOException {
                  super(in);
              }
      
              @Override
              protected Class<?> resolveClass(ObjectStreamClass desc)
                      throws IOException, ClassNotFoundException {
                  Class<?> klazz = classMapping.get(desc.getName());
                  if (klazz != null) {
                      return klazz;
                  } else {
                      return super.resolveClass(desc);
                  }
              }
      
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 2011-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-10-15
        • 1970-01-01
        • 1970-01-01
        • 2021-05-09
        • 2013-03-15
        相关资源
        最近更新 更多