指定在ObjectOutputStream中(未)序列化哪些字段而不使用transient或serialPersistentFields

m0m*_*0m0 7 java reflection serialization

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


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

所以在受影响的类中唯一应该注意的是field-level(@Target(ElementType.FIELD))的注释.

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


该ObjectOutputStream有一个方法writeObjectOverride(Object)可以用来延伸时以限定自己实现的串行化过程的ObjectOutputStream.这仅在ObjectOutputStream使用no-argument-constructor初始化时有效,因为否则writeObjectOverride永远不会调用.但是这种方法要求我自己实现整个序列化过程,我不想这样做,因为它非常复杂并且已经默认实现了ObjectOutputStream.我正在寻找一种方法来修改默认的序列化实现.


另一种方法是ObjectOutputStream再次扩展并覆盖writeObjectOverride(Object)(在调用之后enableReplaceObject(true)).在这个方法中,我尝试使用某种SerializationProxy(参见什么是序列化代理模式?)来将序列化对象封装在代理中,该代理定义了应该序列化的字段列表.但是这种方法也失败了,因为writeObjectOverride也被调用List<SerializedField> fields代理中的字段列表()导致无限循环.

例:

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 {
}
Run Code Online (Sandbox Code Playgroud)

当我发现可以在运行时修改修饰符时(请参阅使用Java反射更改私有静态最终字段)我尝试在运行时设置transient-Modifier,如果设置了相应的注释.不幸的是,这也行不通,因为前一个链接中使用的方法似乎只适用于静态字段.在使用非静态字段进行尝试时,它会在没有异常的情况下运行但不会保留,因为看起来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");    
}
Run Code Online (Sandbox Code Playgroud)

输出是:

1. is NOT transient
2. is transient
3. is NOT transient
Run Code Online (Sandbox Code Playgroud)

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


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


我还尝试了一些其他方法,但正如已经提到的,我无法成功序列化所有字段,除了带有注释的字段DontSerialize.

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


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

Sor*_*har 0

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

不过,有趣的问题。