记一次单例和反射之间的战争

前言

单例大家估计都已经了解过了,这里就不多说了,贴一段概念吧:

单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例。

JAVA中单例的实现方法一般是将构造方法私有化,然后就只有类本身才能new出来自己的实例了,最后后提供一个静态方法将自己new出来的实例返回出去。

单例代码示例

这里贴一段最简单的单例代码实现:

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

大家应该都可以看出来,这是一个饿汉式单例的写法,这样写有没有问题呢?

肯定是有问题的,那就是通过反射来对对象进行实例化,这样系统中就会出现多个实例了,违反了单例设计的初衷。那有没有解决方法呢?

必须有的啊,下面就通过一场“战争”来详细描述单例和反射的博弈过程。

战争开始

第一回合(Singleton VS Destroyer)

单例军团派出了自己的普通士兵(Singleton):

/**
 * 饿汉式单例初版
 * @author ZhengNC
 * @date 2020/5/22 11:32
 */
public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

反射军团也派出了自己的普通士兵(Destroyer):

/**
 * 单例破坏者初版
 * @author ZhengNC
 * @date 2020/5/22 14:07
 */
public class Destroyer {

    public static void main(String[] args){
        destroy();
    }

    /**
     * 破坏初版单例
     * @throws Exception
     */
    public static void destroy(){
        //正常方式获取单例对象
        Singleton instance = Singleton.getInstance();
        Singleton other = null;

        try {
            //通过反射获取单例对象
            Class clazz = Singleton.class;
            Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            other = constructor.newInstance();
        }catch (Exception e){
            System.out.println("抛出了异常:"+e.getCause().getMessage());
        }

        System.out.println(instance);
        System.out.println(other);
        if (other != null && instance != other){
            System.out.println("破坏成功");
        }else {
            System.out.println("破坏失败");
        }
    }
}

结果(Destroyer胜利)

com.test.javademo.singleton.destroy.roundOne.Singleton@1ddc4ec2
com.test.javademo.singleton.destroy.roundOne.Singleton@133314b
破坏成功

反射军团(Destroyer)胜利!

第二回合(SingletonPlus VS Destroyer)

单例军团看到自己的普通士兵不堪一击,于是加强了自己的士兵,在构造方法里加入验证,派出了加强后的士兵(SingletonPlus):

/**
 * 饿汉式单例加强版
 * @author ZhengNC
 * @date 2020/5/22 11:32
 */
public class SingletonPlus {

    private static SingletonPlus instance = new SingletonPlus();

    private SingletonPlus(){
        //防止通过反射调用构造方法创建对象
        if (instance != null){
            throw new RuntimeException("不允许创建多个单例对象");
        }
    }

    public static SingletonPlus getInstance(){
        return instance;
    }
}

反射军团决定乘胜出击,继续派出自己的普通士兵(Destroyer)跟单例军团的加强版士兵(SingletonPlus)战斗。PS:自己实验时别忘了将Destroyer里的Singleton改成SingletonPlus.

结果(SingletonPlus胜利)

抛出了异常:不允许创建多个单例对象
com.test.javademo.singleton.destroy.roundOne.SingletonPlus@1ddc4ec2
null
破坏失败

单例军团(SingletonPlus)胜利!

第三回合(SingletonPlus VS DestroyerPlus)

单例军团继续派出自己的加强版士兵(SingletonPlus)。

而反射军团也对自己的士兵进行了强化(将单例的校验参数改变之后再实例化对象),派出了加强型士兵(DestroyerPlus):

/**
 * 单例破坏者增强版
 * @author ZhengNC
 * @date 2020/5/22 15:07
 */
public class DestroyerPlus {

    public static void main(String[] args){
        destroyPlus();
    }

    /**
     * 破坏增强版单例
     * @throws Exception
     */
    public static void destroyPlus(){
        //正常方式获取单例对象
        SingletonPlus instance = SingletonPlus.getInstance();
        SingletonPlus other = null;

        try {
            //通过反射获取单例对象
            Class clazz = SingletonPlus.class;
            Constructor<SingletonPlus> constructor = clazz.getDeclaredConstructor();
            //通过设置属性值为空来绕过构造方法里的验证
            Field field = clazz.getDeclaredField("instance");
            field.setAccessible(true);
            field.set(null, null);
            constructor.setAccessible(true);
            other = constructor.newInstance();
        }catch (Exception e){
            System.out.println("抛出了异常:"+e.getMessage());
        }

        System.out.println(instance);
        System.out.println(other);
        if (other != null && instance != other){
            System.out.println("破坏成功");
        }else {
            System.out.println("破坏失败");
        }
    }
}

结果(DestroyerPlus胜利)

com.test.javademo.singleton.destroy.roundOne.SingletonPlus@b1bc7ed
com.test.javademo.singleton.destroy.roundOne.SingletonPlus@7cd84586
破坏成功

反射军团(DestroyerPlus)胜利!

第四回合(SingletonPlusPlus VS DestroyerPlus)

单例军团不服气,再次加强了自己的士兵(将校验参数进行final修饰),派出了再次加强版士兵(SingletonPlusPlus):

/**
 * 饿汉式单例再次增强版
 * @author ZhengNC
 * @date 2020/5/22 11:32
 */
public class SingletonPlusPlus {

    //加上final防止反射改变此属性值绕过验证
    private static final SingletonPlusPlus instance = new SingletonPlusPlus();

    private SingletonPlusPlus(){
        //防止通过反射调用构造方法创建对象
        if (instance != null){
            throw new RuntimeException("不允许创建多个单例对象");
        }
    }

    public static SingletonPlusPlus getInstance(){
        return instance;
    }
}

反射军团再次派出上一回合取得胜利的加强版士兵(DestroyerPlus)。PS:自己实验时别忘了将DestroyerPlus里的SingletonPlus改成SingletonPlusPlus.

结果(SingletonPlusPlus胜利)

抛出了异常:Can not set static final com.test.javademo.singleton.destroy.roundOne.SingletonPlusPlus field com.test.javademo.singleton.destroy.roundOne.SingletonPlusPlus.instance to null value
com.test.javademo.singleton.destroy.roundOne.SingletonPlusPlus@b1bc7ed
null
破坏失败

单例军团(SingletonPlusPlus)胜利!

第五回合(SingletonPlusPlus VS DestroyerPlusPlus)

单例军团继续派出自己的再次加强版士兵(SingletonPlusPlus)。

反射军团为了取得胜利已经不择手段,对造士兵的工厂也进行了改造(对反射本身进行反射改造,使其可以修改final修饰的变量),然后推出了再次加强版士兵(DestroyerPlusPlus):

/**
 * 单例破坏者再次增强版
 * @author ZhengNC
 * @date 2020/5/22 15:07
 */
public class DestroyerPlusPlus {

    public static void main(String[] args){
        destroyPlusPlus();
    }

    /**
     * 破坏再次增强版单例
     * @throws Exception
     */
    public static void destroyPlusPlus(){
        //正常方式获取单例对象
        SingletonPlusPlus instance = SingletonPlusPlus.getInstance();
        SingletonPlusPlus other = null;

        try {
            //通过反射获取单例对象
            Class clazz = SingletonPlusPlus.class;
            Constructor<SingletonPlusPlus> constructor = clazz.getDeclaredConstructor();
            //通过设置属性值为空来绕过构造方法里的验证
            Field field = clazz.getDeclaredField("instance");

            //通过修改反射类的属性,将修改final值设置为允许,以便继续修改final属性
            Class fieldClazz = field.getClass();
            Field modifyField = fieldClazz.getDeclaredField("modifiers");
            modifyField.setAccessible(true);
            System.out.println(field.getModifiers());
            modifyField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            System.out.println(field.getModifiers());

            field.setAccessible(true);
            field.set(null, null);
            constructor.setAccessible(true);
            other = constructor.newInstance();
        }catch (Exception e){
            System.out.println("抛出了异常:"+e.getMessage());
        }

        System.out.println(instance);
        System.out.println(other);
        if (other != null && instance != other){
            System.out.println("破坏成功");
        }else {
            System.out.println("破坏失败");
        }
    }
}

结果(DestroyerPlusPlus胜利)

com.test.javademo.singleton.destroy.roundOne.SingletonPlusPlus@7cd84586
com.test.javademo.singleton.destroy.roundOne.SingletonPlusPlus@30dae81
破坏成功

反射军团(DestroyerPlusPlus)取得胜利!

战争结果统计

回合单例军团出战者反射军团出战者胜利者
第一回合SingletonDestroyerDestroyer
第二回合SingletonPlus(构造方法增加校验)DestroyerSingletonPlus
第三回合SingletonPlusDestroyerPlus(先修改校验参数)DestroyerPlus
第四回合SingletonPlusPlus(final修饰校验参数)DestroyerPlusSingletonPlusPlus
第五回合SingletonPlusPlusDestroyerPlusPlus(改造自己可以修改final修饰的参数)DestroyerPlusPlus

结语

记录一下自己学习单例的一个小过程,单例还有很多实现的方法,本文只使用了饿汉式单例来做示例。

后面的单例防御反射的方法暂时还没有想到,希望有知道的读者不吝赐教,非常感激。

再次感谢每一个读我的文章的大牛,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值