------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------
通常我們所使用的單例模式,我們都可以使用反射使它不再單例,如下餓漢式的單例模式:
public final class Singleton{
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
測試案例如下:
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton3 = constructor.newInstance();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton3);
System.out.println(singleton1 == singleton2);
System.out.println(singleton2 == singleton3);
其中singleton1、singleton2都是通過我們所實現的單例模式來獲取的對象,他們應該是同一個對象,singleton3則是通過反射獲取無參構造器,constructor.setAccessible(true)來獲取訪問權限,最後通過無參構造器來創建一個對象singleton3,singleton3和上述兩者應該不是同一個對象,測試結果如下:
說通常我們所使用的單例模式,我們都可以使用反射使它不再單例。然而單例使用枚舉的話,卻可以避免被反射。
public enum Singleton {
instance{
@Override
protected void read() {
System.out.println("read");
}
@Override
protected void write() {
System.out.println("write");
}
};
protected abstract void read();
protected abstract void write();
}
以上是一個單例枚舉的例子,而我們要獲取該實例只需要Singleton.INSTANCE,並且此種方式可以保證該單例線程安全、防反射攻擊、防止序列化生成新的實例。
枚舉單例關於防反射攻擊,當然和枚舉的實現有關,枚舉也是java類,我們對Singleton的class進行反編譯,可以得到一個新的類
public T newInstance(Object ... initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,InvocationTargetException{
if(!override){
if(!Reflection.quickCheckMemberAccess(clazz,modifiers)){
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller,clazz,null,modifiers);
}
}
//如果此類含有ENUM修飾,調用該方法時會直接報錯
if((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccess ca = constructorAccessor;
if(ca == null){
ca = acquireConstructorAccessor();
}
return (T)ca.newInstance(initargs);
}
也就是說反射在通過newInstance創建對象時,會檢查該類是否是枚舉類,如果是,則拋出異常,反射失敗。 也就是說使用枚舉可以避免被反射,從而可以達到單例的效果。
可以發現以下特點:
類的修飾abstract,所以沒法實例化,反射也無能爲力。
關於線程安全的保證,其實是通過類加載機制來保證的,我們看看INSTANCE的實例化時機,是在static塊中,JVM加載類的過程顯然是線程安全的。
對於防止反序列化生成新實例的問題還不是很明白,一般的方法我們會在該類中添加上如下方法,不過枚舉中也沒有顯示的寫明該方法。