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