简体   繁体   中英

Specify which fields are (not) serialized in ObjectOutputStream without using transient or serialPersistentFields

Is there any way to tell an ObjectOutputStream which fields of a serializable class should be serialized without using the keyword transient and without defining an serialPersistentFields -array?


Background: I need to use annotations to define which members of a class should be serialized (or better: not be serialized). The involved classes must implement the interface Serializable , but NOT Externalizable , so I don't want to implement the serialization/deserialization algorithm for each object but rather just use annotations for it. I can not use the transient keyword, because the annotation requires some further checks to determine whether a field should be serialized or not. These checks have to be done by the ObjectOutputStream (or in my own subclass of ObjectOutputStream ). I also cannot define a serialPersistentFields -array in each class, because as explained previously, at compilation time it is not defined which fields should be serialized.

So the only thing that should be notet in the affected class is the annotation at field-level ( @Target(ElementType.FIELD) ).

I've tried quite a lot of approaches in the last few days, but haven't found one which is working:


The ObjectOutputStream has a method writeObjectOverride(Object) which can be used to define an own implementation of the serialization-process when extending ObjectOutputStream . This only works if the ObjectOutputStream is initialized with the no-argument-constructor because otherwise writeObjectOverride is never invoked. But this approach requires me to implement the whole serialization-process by myself and I don't want to do this, as it is quite complex and already implemented by the default ObjectOutputStream . I am looking for a way to just modify the default serialization implementation.


Another approach was extending ObjectOutputStream again and overriding writeObjectOverride(Object) (after calling enableReplaceObject(true) ). In this method, I tried using some kind of SerializationProxy (see What is the Serialization Proxy Pattern? ) to encapsulate the serialized object in a proxy which defines a List of Fields which should be serialized. But this approach also fails as writeObjectOverride then is also called for the List of fields ( List<SerializedField> fields ) in the Proxy resulting in an infinite loop.

Example:

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 {
}

When I found out that it is possible to modify modifiers at runtime (see Change private static final field using Java reflection ) I tried to set the transient-Modifier at runtime if the corresponding annotation was set. Unfortunately this also does not work, because the approach used in the previous link seems to work only on static fields. When trying it with non-static fields it runs without an exception but is not persisted because is looks like Field.class.getDeclaredField(...) returns new instances of the affected fields every time it is called:

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");    
}

The output is:

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

So after calling getDeclaredField again ( Field field2 = clazz.getDeclaredField("anyField"); ) it already lost the transient modifier.


Next approach:
Extend ObjectOutputStream and override ObjectOutputStream.PutField putFields() and define an own PutField-implementation. PutField lets you specify which (additional) fields are serialized but unfortunately the interface only has a lot of methodes of the form put(String name, <type> val) and when implementing these I cannot associate the method calls with the class field it is invoked from. For instance when serializing a field declared as private String test = "foo" the method put("test", "foo") is invoked, but I cannot associate the value of name (which is test ) with the class containing the field test because no reference to the containing class is available and therefore it is impossible to read the annotation noted for the field test .


I also tried a few other approaches but as already mentioned I was not able to successfully serialize all fields except the ones with the annotation DontSerialize present.

One thing I also came across were ByteCode manipulators. Maybe it is possible with these but I have a requirement for not using any external tools - it needs to be pure Java (1.5 or 1.6).


Sorry for this really long post but I just wanted to show what I already tried and am hoping that someone can help me. Thanks in advance.

I would reconsider if "Serialization" is really the thing you want to do. Given that the Serialization rules depends on some logic defined at runtime, the Deserialization process will be a nightmare to write.

Interesting problem, though.

Without rewriting much of Java Serialization, you will need to rewrite the bytecode. At runtime this can be done with Java Agents, but can also be done to class files during the build.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM