【问题标题】:Make Java runtime ignore serialVersionUIDs?让Java运行时忽略serialVersionUIDs?
【发布时间】:2010-12-21 10:54:19
【问题描述】:

我必须处理大量未明确指定 serialVersionUID 的已编译 Java 类。因为它们的 UID 是由编译器任意生成的,所以许多需要序列化和反序列化的类最终会导致异常,即使实际的类定义匹配。 (当然,这都是预期的行为。)

返回并修复所有这些第 3 方代码对我来说是不切实际的。

因此,我的问题是:有没有办法让 Java 运行时忽略 serialVersionUID 中的差异,并且只有在结构存在实际差异时才能反序列化?

【问题讨论】:

    标签: java serialization serialversionuid


    【解决方案1】:

    如果您有权访问代码库,则可以使用SerialVer task for Ant 在可序列化类的源代码中插入和修改serialVersionUID,并一劳永逸地解决问题。

    如果你不能,或者这不是一个选项(例如,如果你已经序列化了一些需要反序列化的对象),一种解决方案是扩展 ObjectInputStream。增强其行为以将流描述符的serialVersionUID 与该描述符表示的本地JVM 中的类的serialVersionUID 进行比较,并在不匹配的情况下使用本地类描述符。然后,只需使用这个自定义类进行反序列化。像这样的东西(感谢this message):

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InvalidClassException;
    import java.io.ObjectInputStream;
    import java.io.ObjectStreamClass;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    
    public class DecompressibleInputStream extends ObjectInputStream {
    
        private static Logger logger = LoggerFactory.getLogger(DecompressibleInputStream.class);
        
        public DecompressibleInputStream(InputStream in) throws IOException {
            super(in);
        }
        
        @Override
        protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
            ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
            Class localClass; // the class in the local JVM that this descriptor represents.
            try {
                localClass = Class.forName(resultClassDescriptor.getName()); 
            } catch (ClassNotFoundException e) {
                logger.error("No local class for " + resultClassDescriptor.getName(), e);
                return resultClassDescriptor;
            }
            ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
            if (localClassDescriptor != null) { // only if class implements serializable
                final long localSUID = localClassDescriptor.getSerialVersionUID();
                final long streamSUID = resultClassDescriptor.getSerialVersionUID();
                if (streamSUID != localSUID) { // check for serialVersionUID mismatch.
                    final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: ");
                    s.append("local serialVersionUID = ").append(localSUID);
                    s.append(" stream serialVersionUID = ").append(streamSUID);
                    Exception e = new InvalidClassException(s.toString());
                    logger.error("Potentially Fatal Deserialization Operation.", e);
                    resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
                }
            }
            return resultClassDescriptor;
        }
    }
    

    【讨论】:

    • 后面的消息链接坏了吗? (六年后,我并不感到惊讶,记住)
    • 非常感谢。事实上,这个 sn-p 也为我节省了一天的时间。
    • 真正的救生员!谢谢!
    • 已经很老了,那怎么用呢?怎么称呼它?
    • 这可以大大简化为return ObjectStreamClass.lookupAny(Class.forName(super.readClassDescriptor().getName()))。不需要任何检查/捕获。
    【解决方案2】:

    解决这个问题有多不切实际?如果你有源代码并且可以重建,你能不能只在整个代码库上运行一个脚本来插入一个

    private long serialVersionUID = 1L;
    

    无处不在?

    【讨论】:

      【解决方案3】:

      使用 CGLIB 将它们插入到二进制类中?

      【讨论】:

      • 好主意,但是......你到底插入了什么值?您需要读取序列化版本的serialVersionUID。这是棘手的部分。
      • 哦,哎呀。是的,你需要知道那里有什么。
      【解决方案4】:

      运行时的序列化错误会明确告诉您 ID 应该是什么。只需更改您的类以将它们声明为 ID,一切都会好起来的。这确实需要您进行更改,但我认为这无法避免

      【讨论】:

        【解决方案5】:

        您可以使用 Aspectj 在加载时将字段“引入”到每个可序列化类中。我将首先使用包将标记接口引入每个类,然后使用 serialVersionUID 的类文件的哈希引入字段

        public aspect SerializationIntroducerAspect {
            
           // introduce marker into each class in the org.simple package
           declare parents: (org.simple.*) implements SerialIdIntroduced;
        
           public interface SerialIdIntroduced{}
           
           // add the field to each class marked with the interface above.
           private long SerialIdIntroduced.serialVersionUID = createIdFromHash(); 
           
           private long SerialIdIntroduced.createIdFromHash()
           {
               if(serialVersionUID == 0)
               {
                   serialVersionUID = getClass().hashCode();
               }
               return serialVersionUID;
           }
        }
        

        您需要将aspectj load time weaver agent 添加到 VM 中,以便它可以将建议编织到您现有的 3rd 方类中。有趣的是,一旦您开始设置 Aspectj,您将使用的次数非常多。

        HTH

        【讨论】:

        • 这是个好主意,但这还不够。问题不在于添加serialVersionUID,问题在于添加与序列化版本相同的serialVersionUID。
        猜你喜欢
        • 1970-01-01
        • 2015-03-03
        • 1970-01-01
        • 2020-01-30
        • 1970-01-01
        • 2016-09-14
        • 1970-01-01
        • 1970-01-01
        • 2020-12-14
        相关资源
        最近更新 更多