介绍
主要介绍通过反射的方式获取单例对象, 验证单例模式的安全性.
主要从以下几个角度来介绍反射下的单例
饿汉式
双重锁检查
枚举单例
饿汉式
饿汉式直接使用反射即可破解单例模式
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 . . .
713338599
168423058
821270929
Process 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 . . .
713338599
168423058
821270929
Process 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.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 实现序列化
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 . . .
258952499
1702297201
1996181658
Process finished with exit code 0
- 修改反序列化方法, 可以防止反序列化
添加以下方法
private Object readResolve() {
return doubleCheckLockLazyPattern;
}
输出结果
D:\jdk1.8\bin\java.exe . . .
258952499
258952499
258952499
Process 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类 源码可以看出, 在反射创建对象时会判断是否是枚举修饰, 是则抛出异常
@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方法, 所以枚举也可以避免反序列化
/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}