Effective Java之对于实例控制,枚举类型优于readResolve(七十七)
這個方法會緊挨著readObject()之后被調用,該方法的返回值將會代替原來反序列化的對象,而原來readObject()反序列化的對象將會立即丟棄。
readObject()方法在序列化單例類時尤其有用。當然,如果使用java5提供的enum來定義枚舉類,則完全不用擔心,程序沒有任何問題。
我們從一個單例模式開始:
public final class MySingleton { private MySingleton() { } private static final MySingleton INSTANCE = new MySingleton(); public static MySingleton getInstance() { return INSTANCE; } }如果實現了序列化,那么會執行readObject方法或默認的序列化。他們都會返回一個新建的實例,也就違反了單例。
readResolve()方法正好滿足需求:
public final class MySingleton { private MySingleton() { } private static final MySingleton INSTANCE = new MySingleton(); public static MySingleton getInstance() { return INSTANCE; } private Object readResolve() throws ObjectStreamException { // instead of the object we're on, // return the class variable INSTANCE return INSTANCE; } }書中提到了如果單例實例中存在非transient對象引用,就會有被攻擊的危險,例子有點難懂,我在這里簡單地解釋一下;
因為單例包含一個非transient對象引用域,這個域內容在Singleton的readResolve方法運行之前被反序列化,于是,攻擊者截胡,把這段readResolve方法運行之前的字節流截下來(不讓他運行readResolve),來個偷梁換柱,把對象引用域指向自己寫的“盜用者”。
于是,把readResolve方法運行之前的字節流 換成 偷梁換柱的字節流,再讓他執行一次,系統看到單例對象有個非transient對象引用域,指向“盜用者”(因為偷梁換柱了),所以系統也會序列化“盜用者”。
盜用者是這樣的:
class ElvisStealer implement Serializable{static Elvis impersonator;private Elvis payload;private Object readResolve(){impersonator = payload;return new String[]{"foolish"};} }序列化盜用者“ElvisStealer”的時候,執行它的readResolve()方法,impersonator = payload;偷偷 把第二次序列化的payload 記錄下來(作為攻擊者,其實沒有必要,只是為了展示),然后返回一個錯誤的對象引用。
但是枚舉類型就完全不同了:
// Enum singleton - the preferred approach public enum Elvis {INSTANCE;private String[] favoriteSongs ={ "Hound Dog", "Heartbreak Hotel" };public void printFavorites() {System.out.println(Arrays.toString(favoriteSongs)); } }這樣做完全沒有后顧之憂,因為枚舉enum生來就是支持序列化的,下面的官方對枚舉的序列化的聲明的翻譯:
在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我們看一下這個valueOf方法:
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum const " + enumType +"." + name); }總結:盡可能用枚舉類型來控制實例,否則,就要必須提供readResolve方法,并確保類的所有實例域都是基本類型或transient的。
總結
以上是生活随笔為你收集整理的Effective Java之对于实例控制,枚举类型优于readResolve(七十七)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Effective Java之保护性编写
- 下一篇: Effective Java之考虑用序列