如何在运行时将类序列化中的字段排除?

37
如何在运行时从序列化过程中排除类字段? 虽然在编译时有“transient”修饰符,但在运行时该怎么办? 我的意思是使用ObjectOutputStream进行常规Java序列化,而不是gson或其他内容。
抱歉,我想我没有解释清楚。这与序列化不完全相关,而与反序列化有关。我有一批旧文件,我像这样处理它们:
public class Deserialize {

/**
 * @param args
 * @throws IOException 
 * @throws ClassNotFoundException 
 */
public static void main(String[] args) throws ClassNotFoundException, IOException {
    File file = new File("/home/developer/workspace/DDFS/some.ddf");
    HackedObjectInputStream in = new HackedObjectInputStream(new GZIPInputStream(new FileInputStream(file)));

    System.out.println("Attempt to open " + file.getAbsolutePath());
    Object obj = in.readObject();
    in.close();


}

 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("DBOBExit", Exit.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))
                {
                    resultClassDescriptor = ObjectStreamClass.lookup(MIGRATION_MAP.get(oldName));   
                }
            }

            return resultClassDescriptor;
        }

    }

这段代码对大多数文件有效,但某些文件会抛出异常

Exception in thread "main" java.lang.ClassCastException: cannot assign instance of java.awt.Polygon to field Exit.msgbackPt of type java.awt.Point in instance of Exit
at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2053)

因为Exit类有不同的版本,新版本有新的字段。 当我在新字段上添加“transient”关键字时,错误消失了,但另一个文件开始抛出异常(最近的文件)。

如果我检测到旧版序列化文件,我是否可以在运行时将这些新字段添加“transient”关键字呢? 也许使用反射或其他方法?


你需要自己实现 writeObject()readObject(),或者使用 Externalizable 替代 Serializable,这样可以完全控制整个过程:http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#4539(以及该规范的其他部分)。请注意,您可能需要正确地反序列化整个类,并在反序列化时处理确定哪些字段已在序列化期间编写的问题(通过在数据之前编写一堆标志或类似的东西)。 - millimoose
为什么?另一端应该发生什么? - user207421
看了你的编辑之后,我觉得你有很多工作要做。如果你想保留数据并演进其架构,内置序列化是一个非常笨重的选择。 - millimoose
我以简单的方式找到了解决方案 https://dev59.com/EmTWa4cB1Zd3GeqPGLg9#14608062 - gaponov
4个回答

63

ObjectOutputStream文档中写道:

对象的默认串行化机制会将对象的类、类签名以及所有非瞬态和非静态字段的值写入。对其他对象的引用(除了在瞬态或静态字段中)也会被同时写入。

因此,当您将一个变量声明为瞬态时,ObjectOutputStream应该忽略它。请确保使用transient关键字而不是@Transient注释。这些注释被一些ORM框架用来标记不应该保存到数据库中的字段,但对于内置串行化框架是无意义的。

private transient String foo; // Field gets ignored by ObjectOutputStream
@Transient private String bar; // Treated normally by ObjectOutputStream (might mean something for some other framework)

1
示例似乎有误。应该忽略瞬态字段而不是非瞬态字段。 - BlueM
1
@BlueM 这就是我写的内容:“当你将变量声明为瞬态时,它应该被ObjectOutputStream忽略”。 - Philipp


0

特定类的序列化表示取决于该类本身,您无法在外部更改它,最接近的方法是定义一个具有自定义序列化行为的子类,但这仅影响该子类的对象,而不是父类型的对象。

如果您根本无法修改所涉及的类,则唯一的选择是子类化ObjectOutputStream并覆盖replaceObject以在写入时用包含您想要的数据的不同对象替换问题对象,并在读取时进行镜像处理(子类化ObjectInputStream并覆盖resolveObject)。


2
不完全是这样。您始终可以使用writeObject()writeReplace() - millimoose

0
你在这里挖错了洞。与其在运行时决定序列化哪些字段并覆盖readClassDescriptor(),不如考虑覆盖readResolve()

这是否意味着我需要将旧版本和新版本的两个类都添加到项目中?应该覆盖哪个类的readResolve方法?整个票据是关于修改后的类(已移动到不同的包并重命名)的反序列化。 - gaponov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接