单例设计模式-反射攻击解决方案及原理

继续使用饿汉式代码

public class Test {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        // 把private方法设置为可访问
        constructor.setAccessible(true);
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(newInstance == instance);

    }
}

结果

com.honor.design.pattern.creational.singleton.HungrySingleton@14ae5a5
com.honor.design.pattern.creational.singleton.HungrySingleton@7f31245a
false

可以看到,通过反射可以创建出一个新的实例。
修改饿汉式单例

public class HungrySingleton implements Serializable {

    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
        if (hungrySingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    private Object readResolve() {
        return hungrySingleton;
    }

}

运行结果

Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
    at com.honor.design.pattern.creational.singleton.HungrySingleton.(HungrySingleton.java:15)
    ... 5 more

使用懒汉式单例模式测试

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class objectClass = LazySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);

        // 顺序不同,结果不同
        LazySingleton instance = LazySingleton.getLazySingleton();
        LazySingleton newInstance = (LazySingleton) constructor.newInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(newInstance == instance);

    }

同时修改私有化构造器

public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton() {
        if (lazySingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static synchronized LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

先使用单例模式。在使用反射的结果

Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
    at com.honor.design.pattern.creational.singleton.LazySingleton.(LazySingleton.java:9)
    ... 5 more

先使用反射,在使用单例

com.honor.design.pattern.creational.singleton.LazySingleton@14ae5a5
com.honor.design.pattern.creational.singleton.LazySingleton@7f31245a
false

修改方法,在私有构造器中添加逻辑


public class LazySingleton {

    private static LazySingleton lazySingleton = null;
    private boolean flag = true;
    private LazySingleton() {
        if (flag) {
            //TODO 可以为任意复杂逻辑
            flag = false;
        } else {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static synchronized LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

}

因为使用反射,可以获取类的所有信息,所以即便添加逻辑,也不能阻止反射成功创建单例

  public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class clazz = LazySingleton.class;
        Constructor c = clazz.getDeclaredConstructor();
        c.setAccessible(true);

        LazySingleton o1 = LazySingleton.getLazySingleton();

        Field flag = o1.getClass().getDeclaredField("flag");
        flag.setAccessible(true);
        flag.setBoolean(o1,true);
        LazySingleton o2 = (LazySingleton) c.newInstance();

        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o1 == o2);

    }

结果
反射攻击成功

com.honor.design.pattern.creational.singleton.LazySingleton@14ae5a5
com.honor.design.pattern.creational.singleton.LazySingleton@7f31245a
false

使用静态类懒汉模式

public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton(){
        if (InnerClass.staticInnerClassSingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }

}

因为静态类懒汉模式,属于类加载的时候就创建了实例,所以可以预见结果是反射攻击失败
测试

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = StaticInnerClassSingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);

        StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();
        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(newInstance == instance);

    }

结果

Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用 
at com.design.pattern.creational.singleton.StaticInnerClassSingleton.init(StaticInnerClassSingleton.java:7)
    ... 5 more

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注