介绍

主要介绍通过反射的方式获取单例对象, 验证单例模式的安全性.
主要从以下几个角度来介绍反射下的单例
饿汉式
双重锁检查
枚举单例

饿汉式

饿汉式直接使用反射即可破解单例模式

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

双重锁检查

双重锁检查同样存在相同的情况

  1. 直接使用
    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
  1. 在双重锁检查私有构造内加入异常
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

  1. 通过序列化反序列化获取对象

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
  1. 修改反序列化方法, 可以防止反序列化

添加以下方法

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");
}