【发布时间】:2011-07-12 16:36:48
【问题描述】:
有没有办法告诉ObjectOutputStream 应该序列化可序列化类的哪些字段而不使用关键字transient 并且不定义serialPersistentFields-array?
背景:我需要使用注解来定义类的哪些成员应该被序列化(或者更好:不被序列化)。所涉及的类必须实现接口Serializable,而不是Externalizable,所以我不想为每个对象实现序列化/反序列化算法,而只是为它使用注释。我不能使用transient 关键字,因为注释需要进一步检查以确定是否应序列化字段。这些检查必须由ObjectOutputStream(或我自己的ObjectOutputStream 子类)完成。我也不能在每个类中定义serialPersistentFields-array,因为如前所述,在编译时没有定义应该序列化哪些字段。
因此,受影响类中唯一需要注意的是字段级别的注释 (@Target(ElementType.FIELD))。
过去几天我尝试了很多方法,但没有找到有效的方法:
ObjectOutputStream 有一个方法writeObjectOverride(Object),可用于在扩展ObjectOutputStream 时定义自己的序列化过程实现。这仅在 ObjectOutputStream 使用 no-argument-constructor 初始化时才有效,否则永远不会调用 writeObjectOverride。但是这种方法需要我自己实现整个序列化过程,我不想这样做,因为它非常复杂并且已经由默认的ObjectOutputStream 实现。我正在寻找一种方法来修改默认的序列化实现。
另一种方法是再次扩展ObjectOutputStream 并覆盖writeObjectOverride(Object)(在调用enableReplaceObject(true) 之后)。在这种方法中,我尝试使用某种 SerializationProxy(请参阅What is the Serialization Proxy Pattern?)将序列化对象封装在代理中,该代理定义应序列化的字段列表。但是这种方法也失败了,因为在代理中的字段列表 (List<SerializedField> fields) 也会调用 writeObjectOverride,从而导致无限循环。
例子:
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),如果设置了相应的注释,我尝试在运行时设置瞬态修饰符。
不幸的是,这也不起作用,因为上一个链接中使用的方法似乎只适用于静态字段。
当尝试使用非静态字段时,它会毫无例外地运行但不会持久化,因为看起来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)。
很抱歉这篇很长的帖子,但我只是想展示我已经尝试过的东西,并希望有人可以帮助我。 提前致谢。
【问题讨论】:
-
读起来有些繁重....
-
我不清楚为什么不能使用瞬态关键字。出于某种原因,您似乎还需要使用 Annotation,但这不会阻止您使用瞬态。
-
也许使用
DontSerialize是一个不好的例子。假设我的注释称为DontSerializeOnMondays(不要质疑此注释的目的,这只是一个示例)。当 ObjectOutputStream 需要序列化一个对象时,它会检查星期几是否是星期一,如果是,它不会序列化与注释关联的字段,而只是序列化类中的所有其他字段。所以在编写包含该字段的类的代码时,我不知道序列化过程是否会在星期一执行 - 所以我不知道我是否应该定义关键字transient。 -
你需要序列化兼容Java序列化吗?还是可以接受其他格式?如果有其他(更简单!)我们可以建议的解决方案,您能告诉我们更多关于您为什么要这样做的原因吗?
-
为了实现一致的反序列化,您总是希望为所有可能序列化的字段存储一些信息。但是,在某些情况下,您可能希望将字段 存储为
null,而不是实际引用的对象。我在ObjectOutputStream中没有看到用于替换被序列化的值的钩子;相关代码深入私有方法。其他序列化框架可能会提供更大的灵活性。或者你可以在被序列化的类中管理这个,方法是有两个字段,一个用于序列化,一个用于其他用途。
标签: java serialization reflection