介绍
主要介绍通过反射的方式获取单例对象, 验证单例模式的安全性.
主要从以下几个角度来介绍反射下的单例
饿汉式
双重锁检查
枚举单例
饿汉式
饿汉式直接使用反射即可破解单例模式
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();// instanceA 和 instanceB 不是同一对象System.out.println(hungerPattern.hashCode());System.out.println(instanceA.hashCode());System.out.println(instanceB.hashCode());} catch (Exception e) {e.printStackTrace();}}}
输出结果
D:\jdk1.8\bin\java.exe . . .713338599168423058821270929Process finished with exit code 0
双重锁检查
双重锁检查同样存在相同的情况
直接使用
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();}}}
输出结果
D:\jdk1.8\bin\java.exe . . .713338599168423058821270929Process finished with exit code 0
在双重锁检查私有构造内加入异常
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;}}
输出结果
D:\jdk1.8\bin\java.exe . . .java.lang.reflect.InvocationTargetExceptionat 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.RuntimeExceptionat com.liuzhihang.demo.singleton.DoubleCheckLockLazyPattern.<init>(DoubleCheckLockLazyPattern.java:15)... 5 more
通过序列化反序列化获取对象
DoubleCheckLockLazyPattern 实现序列化
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();}}}
输出结果
D:\jdk1.8\bin\java.exe . . .25895249917022972011996181658Process finished with exit code 0
- 修改反序列化方法, 可以防止反序列化
添加以下方法
private Object readResolve() {return doubleCheckLockLazyPattern;}
输出结果
D:\jdk1.8\bin\java.exe . . .258952499258952499258952499Process finished with exit code 0
枚举单例
public enum SingletonEnum {/*** 单例*/INSTANCE;private Resource resource;SingletonEnum() {this.resource = new Resource();}public Resource getResource() {return resource;}}class Resource {}
枚举单例分析
在枚举反射获取对象时抛出异常, 通过 Constructor类 源码可以看出, 在反射创建对象时会判断是否是枚举修饰, 是则抛出异常
@CallerSensitivepublic 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 volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}
同时在父类 Enum类 中重写了 readObject方法, 所以枚举也可以避免反序列化
/*** prevent default deserialization*/private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {throw new InvalidObjectException("can't deserialize enum");}
