靜態內部類中引出了反射攻擊的問題
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Test1 { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class objClass = StaticInnerClass.class; //獲取類的構造器 Constructor constructor = objClass.getDeclaredConstructor(); //把構造器私有權限放開 constructor.setAccessible(true); //正常的獲取實例方式 StaticInnerClass staticInnerClass = StaticInnerClass.getInstance(); //反射創建實例 StaticInnerClass newStaticInnerClass = (StaticInnerClass) constructor.newInstance(); System.out.println(staticInnerClass); System.out.println(newStaticInnerClass); System.out.println(staticInnerClass == newStaticInnerClass); } }
上面這個代碼的運行結果:
com.ygz.designpatterns.singleton.StaticInnerClass@4d7e1886
com.ygz.designpatterns.singleton.StaticInnerClass@3cd1a2f1
false
出現了兩個不同的實例,這就違反了我們使用單例原則,不能保證只有一個實例,那么如何解決呢?
public class StaticInnerClass { private static class InnerClass{ private static StaticInnerClass staticInnerClass = new StaticInnerClass(); } public static StaticInnerClass getInstance(){ return InnerClass.staticInnerClass; } private StaticInnerClass(){ //構造器判斷,防止反射攻擊,大家可以在下面這行if判斷打斷點來測試一下這個方法的過程,很好理解的 if(InnerClass.staticInnerClass != null){ throw new IllegalStateException(); } } }
序列化問題
public class SerSingleton implements Serializable {
2 private volatile static SerSingleton uniqueInstance;
3 private String content;
4 public String getContent() {
5 return content;
6 }
7
8 public void setContent(String content) {
9 this.content = content;
10 }
11 private SerSingleton() {
12 }
13
14 public static SerSingleton getInstance() {
15 if (uniqueInstance == null) {
16 synchronized (SerSingleton.class) {
17 if (uniqueInstance == null) {
18 uniqueInstance = new SerSingleton();
19 }
20 }
21 }
22 return uniqueInstance;
23 }
24
25
26 public static void main(String[] args) throws IOException, ClassNotFoundException {
27 SerSingleton s = SerSingleton.getInstance();
28 s.setContent("單例序列化");
29 System.out.println("序列化前讀取其中的內容:"+s.getContent());
30 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
31 oos.writeObject(s);
32 oos.flush();
33 oos.close();
34
35 FileInputStream fis = new FileInputStream("SerSingleton.obj");
36 ObjectInputStream ois = new ObjectInputStream(fis);
37 SerSingleton s1 = (SerSingleton)ois.readObject();
38 ois.close();
39 System.out.println(s+"\n"+s1);
40 System.out.println("序列化后讀取其中的內容:"+s1.getContent());
41 System.out.println("序列化前后兩個是否同一個:"+(s==s1));
42 }
43
44 }
輸出為:
序列化前讀取其中的內容:單例序列化 com.lxp.pattern.singleton.SerSingleton@135fbaa4 com.lxp.pattern.singleton.SerSingleton@58372a00 序列化后讀取其中的內容:單例序列化 序列化前后兩個是否同一個:false
可以看出,序列化前后兩個對象並不想等。為什么會出現這種問題呢?這個講起來,又可以寫一篇博客了,簡單來說“任何一個readObject方法,不管是顯式的還是默認的,它都會返回一個新建的實例,這個新建的實例不同於該類初始化時創建的實例。”當然,這個問題也是可以解決的,想詳細了解的同學可以翻看《effective java》第77條:對於實例控制,枚舉類型優於readResolve。
避免序列化問題
public enum SerEnumSingleton implements Serializable { 2 INSTANCE; 3 private String content; 4 public String getContent() { 5 return content; 6 } 7 public void setContent(String content) { 8 this.content = content; 9 } 10 private SerEnumSingleton() { 11 } 12 13 public static void main(String[] args) throws IOException, ClassNotFoundException { 14 SerEnumSingleton s = SerEnumSingleton.INSTANCE; 15 s.setContent("枚舉單例序列化"); 16 System.out.println("枚舉序列化前讀取其中的內容:"+s.getContent()); 17 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerEnumSingleton.obj")); 18 oos.writeObject(s); 19 oos.flush(); 20 oos.close(); 21 22 FileInputStream fis = new FileInputStream("SerEnumSingleton.obj"); 23 ObjectInputStream ois = new ObjectInputStream(fis); 24 SerEnumSingleton s1 = (SerEnumSingleton)ois.readObject(); 25 ois.close(); 26 System.out.println(s+"\n"+s1); 27 System.out.println("枚舉序列化后讀取其中的內容:"+s1.getContent()); 28 System.out.println("枚舉序列化前后兩個是否同一個:"+(s==s1)); 29 } 30 }
運行結果如下:
1 枚舉序列化前讀取其中的內容:枚舉單例序列化 2 INSTANCE 3 INSTANCE 4 枚舉序列化后讀取其中的內容:枚舉單例序列化 5 枚舉序列化前后兩個是否同一個:true
