靜態內部類中引出了反射攻擊的問題
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