最后更新于 2022 年 5 月 24 日

Featured image of post 单例模式(2)

单例模式(2)

破坏单例模式

破坏双重校验锁的单例

public Lazyman{
    private Lazyman(){}
    private volatile static Lazyman lazyman;
    public static Lazyman getInstance3(){
        if(lazyman==null){
            synchronized (Lazyman.class){
                if(lazyman==null){
                    lazyman=new Lazyman();
                }
            }
        }
        return lazyman;
    }
    //使用反射破坏单例模式
    public static void main(String[] args) throws Exception {
        Lazyman instance = Lazyman.getInstance3();//用单例模式创建
        //用反射创建
        Constructor<Lazyman> declaredConstructor=Lazyman.class.getDeclaredConstructor(null);//1。获取反射对象2。获得一个空参构造器
        declaredConstructor.setAccessible(true);//无视私有的构造器(破除私有权限)
        Lazyman instance2=declaredConstructor.newInstance();//反射创建出来的对象

        //查看用反射创建出来的对象和用单例模式创建出来的对象哈希值一不一样
        System.out.println(instance);
        System.out.println(instance2);
    }
}

运行后的结果:

com.java.Single.Lazyman@2503dbd3
com.java.Single.Lazyman@4b67cf4d

由结果可见单例确实被破坏了

如何防止单例被破坏

declaredConstructor走了一个构造器Lazyman(),所以在Lazyman()里加一个判断。加锁,第二次new的时候就会报错,如果Lazyman不为空,即已经创建过了,此时就知道是来搞破坏,所以抛出一个异常

破坏枚举单例

上一节我们说到枚举是绝对安全的,但是如果我们就想用反射破坏一下,下面来测试一下

失败1:
在EnumSingle.Class这个文件中我们看到里面有一个无参的构造方法,现在我们测试一下是否真的有无参构造

EnumSingle.Class
EnumSingle.Class

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1=EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredconstructor= EnumSingle.class.getDeclaredConstructor(null);
        declaredconstructor.setAccessible(true);
        EnumSingle instance2=declaredconstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

此时爆出了异常,所以我们的测试还没有结束

异常
异常
他告诉我们NoSuchMethodException: com.java.Single.EnumSingle.(),就是说枚举类里没有空参的构造器
此时我们进入Constructor这个构造器
在Constructor这个构造器,Instance里可以看出,正常应该报出“Cannot reflectively create enum objects”这个异常,即不能使用反射破坏枚举。所以我们的探究又失败了。

失败2:
经过上一次失败我们这次通过反编译将EnumSingle.class反编译回来

javap -p EnumSingle.class

反编译EnumSingle.class
反编译EnumSingle.class
此时我们看到里面也有一个空参的构造,说明我们还是被骗了

成功:
经过上面俩次被骗,此时使用一个更专业的工具Jad,用jad反编译EnumSingle.class

Jad(JAva Decompiler)是一个Java的反编译器,可以通过命令行把Java的class文件反编译成源代码。

可以看出是一个有参构造,里面参数是一个string和int,那我们也把测试代码里的参数该成有参的在看看结果

此时我们得倒了想要的结果,我宣布成功了

Built with Hugo