介绍 主要介绍通过反射的方式获取单例对象, 验证单例模式 的安全性. 主要从以下几个角度来介绍反射下的单例 饿汉式 双重锁检查枚举单例
饿汉式 饿汉式 直接使用反射即可破解单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ReflectTest { public static void main (String[] args) { try { HungerPattern hungerPattern = HungerPattern.getHungerPattern(); Class<HungerPattern> hungerPatternClass = HungerPattern.class; Constructor<HungerPattern> conA = hungerPatternClass.getDeclaredConstructor(); Constructor<HungerPattern> conB = hungerPatternClass.getDeclaredConstructor(); conA.setAccessible(true ); conB.setAccessible(true ); HungerPattern instanceA = conA.newInstance(); HungerPattern instanceB = conB.newInstance(); System.out.println(hungerPattern.hashCode()); System.out.println(instanceA.hashCode()); System.out.println(instanceB.hashCode()); } catch (Exception e) { e.printStackTrace(); } } }
输出结果
1 2 3 4 5 6 7 D:\jdk1.8\bin\java.exe . . . 713338599 168423058 821270929 Process finished with exit code 0
双重锁检查 双重锁检查 同样存在相同的情况
直接使用1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class ReflectTest { public static void main (String[] args) { try { DoubleCheckLockLazyPattern pattern = DoubleCheckLockLazyPattern.getDoubleCheckLockLazyPattern(); Class<DoubleCheckLockLazyPattern> patternClass = DoubleCheckLockLazyPattern.class; Constructor<DoubleCheckLockLazyPattern> conA = patternClass.getDeclaredConstructor(); Constructor<DoubleCheckLockLazyPattern> conB = patternClass.getDeclaredConstructor(); conA.setAccessible(true ); conB.setAccessible(true ); DoubleCheckLockLazyPattern patternA = conA.newInstance(); DoubleCheckLockLazyPattern patternB = conA.newInstance(); System.out.println(pattern.hashCode()); System.out.println(patternA.hashCode()); System.out.println(patternB.hashCode()); } catch (Exception e) { e.printStackTrace(); } } }
输出结果
1 2 3 4 5 6 D:\jdk1.8\bin\java.exe . . . 713338599 168423058 821270929 Process finished with exit code 0
在双重锁检查私有构造内加入异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class DoubleCheckLockLazyPattern { private DoubleCheckLockLazyPattern () { // 加入异常判断, 防止反射 if (doubleCheckLockLazyPattern != null) { throw new RuntimeException(); } } private static volatile DoubleCheckLockLazyPattern doubleCheckLockLazyPattern = null; public static DoubleCheckLockLazyPattern getDoubleCheckLockLazyPattern () { try { if (doubleCheckLockLazyPattern == null) { // 一系列操作 Thread.sleep(100); synchronized (DoubleCheckLockLazyPattern.class) { // 二次检查 if (doubleCheckLockLazyPattern == null) { doubleCheckLockLazyPattern = new DoubleCheckLockLazyPattern(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return doubleCheckLockLazyPattern; } }
输出结果
1 2 3 4 5 6 7 8 9 10 11 D:\jdk1.8\bin\java.exe . . . java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.liuzhihang.demo.singleton.ReflectTest.main(ReflectTest.java:24) Caused by: java.lang.RuntimeException at com.liuzhihang.demo.singleton.DoubleCheckLockLazyPattern.<init>(DoubleCheckLockLazyPattern.java:15) ... 5 more
通过序列化反序列化获取对象
DoubleCheckLockLazyPattern 实现序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ReflectTest { public static void main(String[] args) { try { DoubleCheckLockLazyPattern pattern = DoubleCheckLockLazyPattern.getDoubleCheckLockLazyPattern(); FileOutputStream fos= new FileOutputStream("C:/Users/liuzhihang/desktop/pattern.txt" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(pattern); oos.close(); fos.close(); ObjectInputStream oisA = new ObjectInputStream(new FileInputStream("C:/Users/liuzhihang/desktop/pattern.txt" )); DoubleCheckLockLazyPattern patternA= (DoubleCheckLockLazyPattern) oisA.readObject(); ObjectInputStream oisB = new ObjectInputStream(new FileInputStream("C:/Users/liuzhihang/desktop/pattern.txt" )); DoubleCheckLockLazyPattern patternB= (DoubleCheckLockLazyPattern) oisB.readObject(); System.out.println(pattern.hashCode()); System.out.println(patternA.hashCode()); System.out.println(patternB.hashCode()); } catch (Exception e) { e.printStackTrace(); } } }
输出结果
1 2 3 4 5 6 D:\jdk1.8\bin\java.exe . . . 258952499 1702297201 1996181658 Process finished with exit code 0
修改反序列化方法, 可以防止反序列化
添加以下方法
1 2 3 private Object readResolve () { return doubleCheckLockLazyPattern; }
输出结果
1 2 3 4 5 6 D:\jdk1.8\bin\java.exe . . . 258952499 258952499 258952499 Process finished with exit code 0
枚举单例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public enum SingletonEnum { INSTANCE; private Resource resource; SingletonEnum() { this .resource = new Resource (); } public Resource getResource () { return resource; } } class Resource {}
枚举单例分析 在枚举反射获取对象时抛出异常, 通过 Constructor类 源码可以看出, 在反射创建对象时会判断是否是枚举修饰, 是则抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller , clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects" ); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked" ) T inst = (T) ca.newInstance(initargs); return inst; }
同时在父类 Enum类 中重写了 readObject方法, 所以枚举也可以避免反序列化
1 2 3 4 5 6 7 8 /** * prevent default deserialization */ private void readObject(ObjectInputStream in ) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum" ); }