反射攻擊和反序列化問題是兩種最常見的破壞單例模式的手段剥槐,前者利用反射機制修改構(gòu)造函數(shù)的可見性然后強行創(chuàng)建一個新的實例宪摧,后者是將原先的對象序列化后再反序列化從而生成一個新的實例
反射攻擊
產(chǎn)生原因
單例模式實現(xiàn)的一個關(guān)鍵點就是構(gòu)造函數(shù)的私有性,反射攻擊利用反射機制獲取并修改構(gòu)造函數(shù)的可見性蕊苗,從而可以創(chuàng)建出新的實例孩革,破壞單例:
public class Reflection {
public static void normalReflection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
\\利用反射獲取構(gòu)造函數(shù)
Constructor c = StaticInnerClassSingleton.class.getDeclaredConstructor();
\\改變構(gòu)造函數(shù)的可見性
c.setAccessible(true);
\\調(diào)用newInstance創(chuàng)建新的實例
StaticInnerClassSingleton o1 = (StaticInnerClassSingleton) c.newInstance();
\\通過getInstance方法獲取單例
StaticInnerClassSingleton o2 = StaticInnerClassSingleton.getInstance();
System.out.println(o1);
System.out.println(o2);
}
運行結(jié)果
design.Singleton.StaticInnerClassSingleton@1d44bcfa
design.Singleton.StaticInnerClassSingleton@266474c2
Process finished with exit code 0
通過運行結(jié)果可以發(fā)現(xiàn),兩個實例不是同一個熔掺,單例模式被破壞非剃。
枚舉防止反射攻擊
JDK不允許通過反射創(chuàng)建新的枚舉實例,所以枚舉單例不會被反射攻擊破壞:
public static void enumReflection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor c = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
c.setAccessible(true);
EnumSingleton o1 = (EnumSingleton) c.newInstance();
EnumSingleton o2 = EnumSingleton.getInstance();
System.out.println(o1);
System.out.println(o2);
}
運行結(jié)果:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at design.Singleton.Reflection.enumReflection(Reflection.java:22)
at design.Singleton.Reflection.main(Reflection.java:30)
Process finished with exit code 1
反序列化攻擊
產(chǎn)生原因
對象序列化再反序列化時券坞,會生成一個新的對象恨锚,從而破壞單例:
public static void normalSingletonDeserializeProblem() throws IOException, ClassNotFoundException {
StaticInnerclassSingleton o1 = StaticInnerclassSingleton.getInstance();
//通過writeObject將對象序列化到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(o1);
//讀取文件倍靡,反序列化
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
StaticInnerclassSingleton newInstance = (StaticInnerclassSingleton) ois.readObject();
System.out.println(o1);
System.out.println(newInstance);
System.out.println(StaticInnerclassSingleton.getInstance());
}
運行結(jié)果:
design.Singleton.StaticInnerclassSingleton@63947c6b
design.Singleton.StaticInnerclassSingleton@34a245ab
design.Singleton.StaticInnerclassSingleton@63947c6b
Process finished with exit code 0
反序列化之后,生成了一個新的對象他挎,單例被破壞
解決方法
通過實現(xiàn)一個readResolve方法捡需,可以避免反序列化問題
class NoProblemSingleton implements Serializable {
private NoProblemSingleton() {}
public static NoProblemSingleton getInstance() {
return InnerHolder.INSTANCE;
}
private static class InnerHolder {
private static final NoProblemSingleton INSTANCE = new NoProblemSingleton();
}
//實現(xiàn)readResolve方法可以解決反序列化攻擊,反序列化時會檢查有沒有這個方法呢撞,如果有可以調(diào)用這個方法返回對象
private Object readResolve() {
return InnerHolder.INSTANCE;
}
}
運行結(jié)果:
design.Singleton.NoProblemSingleton@63947c6b
design.Singleton.NoProblemSingleton@63947c6b
design.Singleton.NoProblemSingleton@63947c6b
true
Process finished with exit code 0
實現(xiàn)readResolve方法后庵寞,反序列化的對象和newInstance返回的對象是同一個
原理
反序列化調(diào)用readObject方法時,會判斷類中是否有readResolve方法捐川。如果有逸尖,會調(diào)用readResolve方法并最終用readResolve方法返回的對象替換反序列化出來的對象。不過替換是發(fā)生在反序列化之后的岩齿,也就是說反序列化的對象還是生成了苞俘,只不過無法被訪問只能等待GC回收。
枚舉類型防止反序列化攻擊
枚舉單例不會有反序列化問題乞封,因為readObject如果判斷反序列化的對象是枚舉類型,會直接調(diào)用Enum.valueOf方法返回枚舉實例
總結(jié)
- 反射攻擊和反序列化是單例模式的兩個最常見的問題
- 在單例類中實現(xiàn)readResolve方法可以避免反序列化問題
- 枚舉既不存在反射問題肃晚,也不存在反序列化問題