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

时间:2011-03-10 12:33:34

标签: java serialization reflection

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


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

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

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


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


另一种方法是再次扩展ObjectOutputStream并覆盖writeObjectOverride(Object)(在调用enableReplaceObject(true)之后)。在这个方法中,我尝试使用某种SerializationProxy(参见What is the Serialization Proxy Pattern?)将序列化对象封装在代理中,该代理定义了应该序列化的字段列表。但是这种方法也失败了,因为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 {
}

当我发现可以在运行时修改修饰符时(参见Change private static final field using Java reflection)我尝试在运行时设置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");    
}

输出结果为:

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, <type> val)形式的方法,并且在实现这些时,我无法将方法调用与调用它的类字段相关联。例如,在序列化声明为private String test = "foo"的字段时,调用方法put("test", "foo"),但我无法将name(即test)的值与包含该字段的类相关联test因为没有对包含类的引用,因此无法读取为字段test标注的注释。


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

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


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

2 个答案:

答案 0 :(得分:0)

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

有趣的问题。

答案 1 :(得分:0)

无需重写大部分Java序列化,您将需要重写字节码。在运行时,可以使用Java代理完成此操作,也可以在构建过程中对文件进行分类。