美文网首页
单例模式与反射的博弈

单例模式与反射的博弈

作者: bin丶 | 来源:发表于2021-09-20 14:30 被阅读0次

    单例模式与反射的博弈

    1. 单例模式介绍

    单例模式的核心概念是:私有化构造器,私有化静态对象属性,对外公开获取对象属性的方法,

    从而使得外部类引用该类时,只存在唯一的一个对象。

    2. 饿汉式单例模式代码

    • 饿汉式是最简单的一种实现方式,但是失去了 lazy loading (懒加载)的特性,被 final 和 static 同时修饰的属性会在类的准备阶段完成赋值
    public class Singleton_1 {
    
        // 1. 私有化构造器
        private Singleton_1() {}
    
        // 2. 本类内部创建静态常量型实例
        private final static Singleton_1 instance = new Singleton_1();
    
        // 3. 对外提供公有的获取实例方法
        public static Singleton_1 getInstance() {
            return instance;
        }
    
    }
    

    3. 使用反射获取私有化构造器破解单例

            // 正常方式获得的对象
            Singleton_1 instance = Singleton_1.getInstance();
            // 获得class 对象
            Class<? extends Singleton_1> singleton_class = instance.getClass();
            Constructor<? extends Singleton_1> constructor = singleton_class.getDeclaredConstructor(); // 获取无参构造器
            constructor.setAccessible(true); // 给予私有构造器的使用权限
          
            // 使用构造器创建新的实例
            Singleton_1 singleton_1 = constructor.newInstance();
            System.out.println("创建新实例成功,破解成功...");
            System.out.println(instance.hashCode());
            System.out.println(singleton_1.hashCode());
            System.out.println(instance == singleton_1); // 对象比较
       
    
    • 输出结果如下:
    反射破解单例.jpg

    4. 单例模式在构造器中加入验证防止反射使用构造器

    代码如下:

        // 防止反射破坏
        private static boolean flag = true;
    
        // 1. 私有化构造器
        private Singleton_1() {
            if (flag) {
                flag = false; // 在第一次构造完实例后将不能使用
            } else {
                throw new RuntimeException("单例模式遇到攻击,第二个对象未创建成功");
            }
        }
    

    输出如下:


    单例验证构造器.jpg

    5. 反射修改flag验证

    代码如下:

            Field flag = singleton_class.getDeclaredField("flag");
            flag.setAccessible(true);
            System.out.println(flag.get(instance));
            flag.set(instance, true); // 修改flag值为true
            System.out.println(flag.get(instance));
    
    在修改完flag属性后,依旧能够破解单例模式。而Enum(枚举)独有的一些特性让反射不能够使用私有构造器去创建新的实例,因此推荐使用Enum来设计单例模式

    6. Enum单例

    • 简洁好用
    public enum SingletonEnum {
        INSTANCE;
        public void sayOK() {
            System.out.println("ok~");
        }
    }
    
    • 测试代码
    class Test_Enum {
        public static void main(String[] args) throws Exception{
            SingletonEnum instance_1 = SingletonEnum.INSTANCE;
            SingletonEnum instance_2 = SingletonEnum.INSTANCE;
            System.out.println("正常情况下,两个对象是否相同? " + (instance_1 == instance_2));
    
            // 使用反射
            Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            SingletonEnum newInstance = constructor.newInstance();
            System.out.println("使用反射,能否创建不同实例?" + (instance_1 == newInstance));
        }
    }
    
    • 输出结果
    枚举单例测试.jpg
    结果是直接报错,因为Enum(枚举)并没有无参构造器~ 不信就看下面代码
    public abstract class Enum<E extends Enum<E>>
            implements Comparable<E>, Serializable {
    
        private final String name;
    
        public final String name() {
            return name;
        }
        
        private final int ordinal;
    
        public final int ordinal() {
            return ordinal;
        }
        
        protected Enum(String name, int ordinal) {
            this.name = name;
            this.ordinal = ordinal;
        }
    

    定义枚举相当于自动继承了Enum类,而Enum类本身只有一个构造器,那就是 protected Enum(String name, int ordinal), 所以我们获取不到无参构造器。

    那么用有参构造器会怎么样?
    class Test_Enum {
        public static void main(String[] args) throws Exception{
            SingletonEnum instance_1 = SingletonEnum.INSTANCE;
            SingletonEnum instance_2 = SingletonEnum.INSTANCE;
            System.out.println("正常情况下,两个对象是否相同? " + (instance_1 == instance_2));
            // 拿enum定义的唯一的构造器
            Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);
            SingletonEnum newInstance = constructor.newInstance("Test1", 11);
            System.out.println("使用反射,能否创建不同实例?" + (instance_1 == newInstance));
        }
    }
    
    • 输出结果
    反射枚举失败.jpg
    通过第二行异常点进去看到源码
    public T newInstance(Object ... initargs)
            throws InstantiationException, IllegalAccessException,
                   IllegalArgumentException, InvocationTargetException
        {
            ****省略****
            if ((clazz.getModifiers() & Modifier.ENUM) != 0) // 如果反射的类被Enum修饰,直接抛异常  
                throw new IllegalArgumentException("Cannot reflectively create enum objects");
            ****省略****
     } 
    

    相关文章

      网友评论

          本文标题:单例模式与反射的博弈

          本文链接:https://www.haomeiwen.com/subject/alomgltx.html