Java单例---反射攻击单例和解决方法
静态内部类中引出了反射攻击的问题
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