2011-03-10 23 views
5

有沒有什麼辦法告訴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)。


對不起,這個真的很長的帖子,但我只是想顯示我已經嘗試過,並希望有人能幫助我。 在此先感謝。

+1

這是一些沉重的閱讀.... – 2011-03-10 12:37:02

+0

它不清楚爲什麼你不能使用瞬變關鍵字。看起來你還需要使用註釋出於某種原因,但這並不能阻止你使用瞬態。 – 2011-03-10 12:44:26

+1

也許使用'DontSerialize'是一個不好的例子。比方說我的註釋被稱爲'DontSerializeOnMondays'(不要質疑這個註釋的目的,它只是一個例子)。當ObjectOutputStream需要序列化一個對象時,它會檢查星期幾是否爲星期一,如果是,它不會序列化註釋關聯的字段,而是序列化類中的所有其他字段。所以在編寫包含字段的類的代碼時,我不知道序列化過程是否會在星期一或星期一執行 - 所以我不知道是否應該定義關鍵字'transient'。 – m0m0 2011-03-10 13:45:13

回答

0

我會重新考慮一下,如果「序列化」真的是你想要做的事情。鑑於序列化規則依賴於運行時定義的某些邏輯,因此反序列化過程將是一個噩夢。

雖然有趣的問題。

相關問題