指定哪些字段在 ObjectOutputStream 中序列化(不序列化),而不使用瞬态或serialPersistentFields
有没有办法告诉ObjectOutputStream
可序列化类的哪些字段应该被序列化,而不使用关键字transient
并且不定义serialPersistentFields
数组?
背景:我需要使用注释来定义类的哪些成员应该被序列化(或者更好:不被序列化)。涉及的类必须实现接口Serialized
,但不能实现Externalized
,所以我不想为每个对象实现序列化/反序列化算法,而只是使用注释。我无法使用 transient
关键字,因为注释需要一些进一步的检查来确定字段是否应该序列化。这些检查必须由 ObjectOutputStream
(或在我自己的 ObjectOutputStream
子类中)完成。我也无法在每个类中定义一个 serialPercientFields
数组,因为如前所述,在编译时未定义应序列化哪些字段。
因此,在受影响的类中唯一需要注意的是字段级别的注释 (@Target(ElementType.FIELD)
)。
在过去的几天里,我尝试了很多方法,但还没有找到一种有效的方法:
ObjectOutputStream
有一个方法writeObjectOverride(Object)
,它可以用于在扩展 ObjectOutputStream 时定义自己的序列化过程实现。仅当 ObjectOutputStream
使用无参数构造函数初始化时才有效,否则永远不会调用 writeObjectOverride
。但这种方法要求我自己实现整个序列化过程,但我不想这样做,因为它非常复杂并且已经由默认的 ObjectOutputStream
实现。我正在寻找一种仅修改默认序列化实现的方法。
另一种方法是再次扩展ObjectOutputStream
并覆盖writeObjectOverride(Object)
(在调用enableReplaceObject(true)
之后)。在这种方法中,我尝试使用某种 SerializationProxy (请参阅 什么是序列化代理模式? )将序列化对象封装在代理中,该代理定义了应序列化的字段列表。但这种方法也会失败,因为 writeObjectOverride 然后也会为代理中的字段列表(List
)调用,从而导致无限循环。
示例:
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 {
}
当我发现可以在运行时修改修饰符时(请参阅 使用Java反射更改私有静态最终字段)如果设置了相应的注释,我尝试在运行时设置瞬态修饰符。 不幸的是,这也不起作用,因为上一个链接中使用的方法似乎只适用于静态字段。 当尝试使用非静态字段时,它运行时不会出现异常,但不会持久化,因为看起来像 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,
形式的方法,并且在实现这些方法时,我无法关联方法使用调用它的类字段进行调用。例如,当序列化声明为 private String test = "foo"
的字段时,会调用方法 put("test", "foo")
,但我无法关联该值name
(即 test
)与包含字段 test
的类,因为没有对包含类的引用可用,因此不可能阅读字段 test
的注释。
我还尝试了其他一些方法,但正如已经提到的,我无法成功序列化除带有注释 DontSerialize
的字段之外的所有字段。
我还遇到的一件事是字节码操纵器。也许这些是可能的,但我要求不使用任何外部工具 - 它需要是纯Java(1.5或1.6)。
很抱歉这篇很长的文章,但我只是想展示我已经尝试过的内容,并希望有人可以帮助我。 提前致谢。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我会重新考虑“序列化”是否真的是您想做的事情。鉴于序列化规则取决于运行时定义的某些逻辑,因此反序列化过程将是编写的一场噩梦。
不过,有趣的问题。
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.
如果不重写大部分 Java 序列化,您将需要重写字节码。在运行时,这可以使用 Java 代理来完成,但也可以在构建期间对类文件完成。
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.