有沒有什麼辦法告訴ObjectOutputStream
可序列化類的哪些字段應該被序列化而不使用關鍵字transient
並且沒有定義serialPersistentFields
-array?指定哪些字段在ObjectOutputStream中沒有使用transient或serialPersistentFields序列化
背景:我需要使用註釋來定義應該序列化一個類的哪些成員(或更好:不是序列化)。涉及的類必須實現接口Serializable
,但不是Externalizable
,所以我不想爲每個對象實現序列化/反序列化算法,而只是爲它使用註釋。我無法使用關鍵字transient
,因爲註釋需要進一步檢查以確定字段是否應該序列化。這些檢查必須由ObjectOutputStream
(或我自己的ObjectOutputStream
的子類)完成。我也不能在每個類中定義一個serialPersistentFields
-array,因爲如前所述,在編譯時,沒有定義哪些字段應該被序列化。
因此,唯一應該在受影響的類別中進行註釋的是字段級別的註釋(@Target(ElementType.FIELD)
)。
我已經嘗試了很多辦法,在過去的幾天裏,但還沒有找到一個這是工作:
的ObjectOutputStream
有一個方法writeObjectOverride(Object)
可以用來定義自己的在擴展ObjectOutputStream
時執行序列化過程。這僅在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)我試圖在運行時,如果相應的註釋被設定爲設定的瞬態 - 修飾。 不幸的是,這也不起作用,因爲上一個鏈接中使用的方法似乎只適用於靜態字段。 當與非靜態字段嘗試它運行沒有異常,但不持久,因爲就是看起來每次調用的時候像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)。
對不起,這個真的很長的帖子,但我只是想顯示我已經嘗試過,並希望有人能幫助我。 在此先感謝。
這是一些沉重的閱讀.... – 2011-03-10 12:37:02
它不清楚爲什麼你不能使用瞬變關鍵字。看起來你還需要使用註釋出於某種原因,但這並不能阻止你使用瞬態。 – 2011-03-10 12:44:26
也許使用'DontSerialize'是一個不好的例子。比方說我的註釋被稱爲'DontSerializeOnMondays'(不要質疑這個註釋的目的,它只是一個例子)。當ObjectOutputStream需要序列化一個對象時,它會檢查星期幾是否爲星期一,如果是,它不會序列化註釋關聯的字段,而是序列化類中的所有其他字段。所以在編寫包含字段的類的代碼時,我不知道序列化過程是否會在星期一或星期一執行 - 所以我不知道是否應該定義關鍵字'transient'。 – m0m0 2011-03-10 13:45:13