在我們通常的單例方法中,通常有兩種方法來擊穿單例,反射和序列化
使用單元素枚舉可以有效的解決這兩個問題(在最后)
反射擊穿單例
這是一個普通的單例的例子
public class Singleton {
private static final Singleton SINGLETON = new Singleton();
public static Singleton getSingleton(){
return SINGLETON;
}
}
用反射擊穿
public class App {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = Singleton.class;
Constructor<?> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
// 單例對象
Singleton singleton = (Singleton) c.newInstance();
// 反射創(chuàng)建的對象
Singleton singleton1 = Singleton.getSingleton();
// 結(jié)果是false 證明并不是同一個對象
System.out.println(singleton == singleton1);
}
}
如何防止反射擊穿呢?
我們在構(gòu)造方法中進(jìn)行一次判斷
public class Singleton {
private static final Singleton SINGLETON = new Singleton();
private Singleton() {
synchronized (Singleton.class) {
if (SINGLETON != null){
throw new RuntimeException("試圖破壞單例模式");
}
}
}
public static Singleton getSingleton() {
return SINGLETON;
}
}
public class App {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 單例對象
Singleton singleton1 = Singleton.getSingleton();
// 利用反射創(chuàng)建對象
Class<?> clazz = Singleton.class;
Constructor<?> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
// 到這里的時候會報錯
Singleton singleton = (Singleton) c.newInstance();
//比較
System.out.println(singleton == singleton1);
}
}
序列化擊穿單例
經(jīng)過序列化再反序列化的對象已經(jīng)和原對象是兩個對象了(這里我就不舉例子了)可以看看本文最后的鏈接
如果需要實例化,我們必須添加一個方法.
private Object readResolve(){
return SINGLETON;
}
引用一下文檔的說明
This writeReplace method is invoked by serialization if the method
exists and it would be accessible from a method defined within the
class of the object being serialized. Thus, the method can have private,
protected and package-private access. Subclass access to this method
follows java accessibility rules.
Classes that need to designate a replacement when an instance of it
is read from the stream should implement this special method with the
exact signature.
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
This readResolve method follows the same invocation rules and
accessibility rules as writeReplace.
簡單來說反序列化時可以用這個方法返回的對象替換反序列化對象
枚舉單例
枚舉單例十分簡單,并且無法被反射和序列化(自己就能序列化)擊穿
public enum EnumSingleton {
SINGLETON;
public void doSomeThing(){
// do what you what to do
}
}
EnumSingleton.SINGLETON.doSomeThing();
作者的話
單元素的枚舉類型已經(jīng)成為實現(xiàn)Singleton的最佳方法