有没有办法告诉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)。
很抱歉这个很长的帖子,但我只想表明我已经尝试过的东西,并希望有人可以帮助我。 提前谢谢。
答案 0 :(得分:0)
我会重新考虑“序列化”是否真的是你想做的事情。鉴于序列化规则依赖于在运行时定义的某些逻辑,反序列化过程将是一个噩梦。
有趣的问题。
答案 1 :(得分:0)
无需重写大部分Java序列化,您将需要重写字节码。在运行时,可以使用Java代理完成此操作,也可以在构建过程中对文件进行分类。