【问题标题】:Specify which fields are (not) serialized in ObjectOutputStream without using transient or serialPersistentFields在不使用瞬态或 serialPersistentFields 的情况下,指定 ObjectOutputStream 中哪些字段(不)序列化
【发布时间】:2011-07-12 16:36:48
【问题描述】:

有没有办法告诉ObjectOutputStream 应该序列化可序列化类的哪些字段而不使用关键字transient 并且不定义serialPersistentFields-array?


背景:我需要使用注解来定义类的哪些成员应该被序列化(或者更好:不被序列化)。所涉及的类必须实现接口Serializable,而不是Externalizable,所以我不想为每个对象实现序列化/反序列化算法,而只是为它使用注释。我不能使用transient 关键字,因为注释需要进一步检查以确定是否应序列化字段。这些检查必须由ObjectOutputStream(或我自己的ObjectOutputStream 子类)完成。我也不能在每个类中定义serialPersistentFields-array,因为如前所述,在编译时没有定义应该序列化哪些字段。

因此,受影响类中唯一需要注意的是字段级别的注释 (@Target(ElementType.FIELD))。

过去几天我尝试了很多方法,但没有找到有效的方法:


ObjectOutputStream 有一个方法writeObjectOverride(Object),可用于在扩展ObjectOutputStream 时定义自己的序列化过程实现。这仅在 ObjectOutputStream 使用 no-argument-constructor 初始化时才有效,否则永远不会调用 writeObjectOverride。但是这种方法需要我自己实现整个序列化过程,我不想这样做,因为它非常复杂并且已经由默认的ObjectOutputStream 实现。我正在寻找一种方法来修改默认的序列化实现。


另一种方法是再次扩展ObjectOutputStream 并覆盖writeObjectOverride(Object)(在调用enableReplaceObject(true) 之后)。在这种方法中,我尝试使用某种 SerializationProxy(请参阅What is the Serialization Proxy Pattern?)将序列化对象封装在代理中,该代理定义应序列化的字段列表。但是这种方法也失败了,因为在代理中的字段列表 (List<SerializedField> fields) 也会调用 writeObjectOverride,从而导致无限循环。

例子:

public class AnnotationAwareObjectOutputStream extends ObjectOutputStream {    
    public AnnotationAwareObjectOutputStream(OutputStream out)
            throws IOException {
        super(out);
        enableReplaceObject(true);
    }

    @Override
    protected Object replaceObject(Object obj) throws IOException {
        try {
            return new SerializableProxy(obj);
        } catch (Exception e) {
            return new IOException(e);
        }
    }

    private class SerializableProxy implements Serializable {
        private Class<?> clazz;
        private List<SerializedField> fields = new LinkedList<SerializedField>();

        private SerializableProxy(Object obj) throws IllegalArgumentException,
                IllegalAccessException {
            clazz = obj.getClass();
            for (Field field : getInheritedFields(obj.getClass())) {
                // add all fields which don't have an DontSerialize-Annotation
                if (!field.isAnnotationPresent(DontSerialize.class))
                    fields.add(new SerializedField(field.getType(), field
                            .get(obj)));
            }
        }

        public Object readResolve() {
            // TODO: reconstruct object of type clazz and set fields using
            // reflection
            return null;
        }
    }

    private class SerializedField {
        private Class<?> type;
        private Object value;

        public SerializedField(Class<?> type, Object value) {
            this.type = type;
            this.value = value;
        }
    }

    /** return all fields including superclass-fields */
    public static List<Field> getInheritedFields(Class<?> type) {
        List<Field> fields = new ArrayList<Field>();
        for (Class<?> c = type; c != null; c = c.getSuperclass()) {
            fields.addAll(Arrays.asList(c.getDeclaredFields()));
        }
        return fields;
    }

}

// I just use the annotation DontSerialize in this example for simlicity.
// Later on I want to parametrize the annotation and do some further checks
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DontSerialize {
}

当我发现可以在运行时修改修饰符时(参见Change private static final field using Java reflection),如果设置了相应的注释,我尝试在运行时设置瞬态修饰符。 不幸的是,这也不起作用,因为上一个链接中使用的方法似乎只适用于静态字段。 当尝试使用非静态字段时,它会毫无例外地运行但不会持久化,因为看起来Field.class.getDeclaredField(...) 每次调用时都会返回受影响字段的新实例:

public void setTransientTest() throws SecurityException,
            NoSuchFieldException, IllegalArgumentException,
            IllegalAccessException {
        Class<MyClass> clazz = MyClass.class;
        // anyField is defined as "private String anyField"
        Field field = clazz.getDeclaredField("anyField");

        System.out.println("1. is "
                + (Modifier.isTransient(field.getModifiers()) ? "" : "NOT ")
                + "transient");

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        boolean wasAccessible = modifiersField.isAccessible();
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() | Modifier.TRANSIENT);
        modifiersField.setAccessible(wasAccessible);

        System.out.println("2. is "
                + (Modifier.isTransient(field.getModifiers()) ? "" : "NOT ")
                + "transient");

        Field field2 = clazz.getDeclaredField("anyField");

        System.out.println("3. is "
                + (Modifier.isTransient(field2.getModifiers()) ? "" : "NOT ")
                + "transient");    
}

输出是:

1. is NOT transient
2. is transient
3. is NOT transient

所以在再次调用 getDeclaredField (Field field2 = clazz.getDeclaredField("anyField");) 之后,它已经失去了瞬态修饰符。


下一个方法:
扩展 ObjectOutputStream 并覆盖 ObjectOutputStream.PutField putFields() 并定义自己的 PutField 实现。 PutField 允许您指定哪些(附加)字段被序列化,但不幸的是,该接口只有很多 put(String name, &lt;type&gt; val) 形式的方法,并且在实现这些方法时,我无法将方法调用与调用它的类字段相关联。例如,当序列化声明为private String test = "foo" 的字段时,会调用方法put("test", "foo"),但我无法将name(即test)的值与包含字段test 的类相关联,因为没有引用包含类可用,因此无法阅读为字段test 注明的注释。


我还尝试了其他一些方法,但如前所述,除了带有注释 DontSerialize 的字段之外,我无法成功序列化所有字段。

我还遇到了 ByteCode 操纵器。也许这些是可能的,但我要求不使用任何外部工具 - 它需要是纯 Java(1.5 或 1.6)。


很抱歉这篇很长的帖子,但我只是想展示我已经尝试过的东西,并希望有人可以帮助我。 提前致谢。

【问题讨论】:

  • 读起来有些繁重....
  • 我不清楚为什么不能使用瞬态关键字。出于某种原因,您似乎还需要使用 Annotation,但这不会阻止您使用瞬态。
  • 也许使用DontSerialize 是一个不好的例子。假设我的注释称为DontSerializeOnMondays(不要质疑此注释的目的,这只是一个示例)。当 ObjectOutputStream 需要序列化一个对象时,它会检查星期几是否是星期一,如果是,它不会序列化与注释关联的字段,而只是序列化类中的所有其他字段。所以在编写包含该字段的类的代码时,我不知道序列化过程是否会在星期一执行 - 所以我不知道我是否应该定义关键字transient
  • 你需要序列化兼容Java序列化吗?还是可以接受其他格式?如果有其他(更简单!)我们可以建议的解决方案,您能告诉我们更多关于您为什么要这样做的原因吗?
  • 为了实现一致的反序列化,您总是希望为所有可能序列化的字段存储一些信息。但是,在某些情况下,您可能希望将字段 存储为null,而不是实际引用的对象。我在ObjectOutputStream 中没有看到用于替换被序列化的值的钩子;相关代码深入私有方法。其他序列化框架可能会提供更大的灵活性。或者你可以在被序列化的类中管理这个,方法是有两个字段,一个用于序列化,一个用于其他用途。

标签: java serialization reflection


【解决方案1】:

我会重新考虑“序列化”是否真的是您想要做的事情。鉴于序列化规则依赖于运行时定义的一些逻辑,反序列化过程将是一场噩梦。

不过,这个问题很有趣。

【讨论】:

    【解决方案2】:

    无需重写大部分 Java 序列化,您将需要重写字节码。在运行时,这可以通过 Java 代理完成,但也可以在构建期间对类文件完成。

    【讨论】:

      猜你喜欢
      • 2012-04-04
      • 2016-07-01
      • 1970-01-01
      • 2012-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多