反射使单例模式可以创建不止一个对象?

1,230 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

反射

(对前面概念不感兴趣的小伙伴可以直接跳转到下面)

1. 定义

image-20220811110007708

反向探知,在程序运行过程中动态的获取类的相关属性

这种动态获取类的内容以及动态调用对象的方法和获取属性的机制叫做JAVA的反射机制

/**
 * @Author: sc
 * @Date: 2022/8/11 10:32
 */
public class test {
​
    public static void main(String[] args) throws Exception {
        // 获取一个类对象
        Class<Person> personClass = Person.class;
​
        // 获取类对象对应的属性或者方法
        System.out.println(personClass.getName());
        System.out.println(personClass.getPackage());
        System.out.println(personClass.getClassLoader());
        System.out.println(personClass.getSuperclass());
​
        // 获取一个实例
        Person person = personClass.newInstance();
        // 获取类中的方法
        Method method = personClass.getDeclaredMethod("getUsername");
        // 通过反射执行方法
        method.invoke(person);
    }
​
}

执行结果:

image-20220811112106416

image-20220811111420256

也可以通过传入key来选择建立对应的类

image-20220811140427195

image-20220811140337230

image-20220811140358182

反射到底慢在哪里?

  1. 调用了native方法
  2. 每次创建一个对象都需要安全检查

2. 反射基础操作

获取对象的四种方法和对象属性

image-20220811150136054

field操作

可以看到jdk文档里,modifier的值

image-20220811142659783

就是都是二的进制,意思就是有没有对应的constant field 比如PRIVATE、NATIVE关键字,有就是1,反之0

image-20220811143133750

也可以增加类中的属性

应用: 这个对象互补类

public class ClassExamine {
​
    /**
     * 对象字段互补。传入一个同类型被补充对象和完整对象,如果被补充对象中有字段为null或者字符串为空,就用完整对象对应的字段值补上去;如果被补充对象中某字段不为空则保留它自己的值。
     *
     * @param origin       被补充对象
     * @param intactObject 完整对象
     * @param <T>          传入对象类型
     */
    public static <T> void objectOverlap(T origin, T intactObject) throws Exception {
        Field[] fields = origin.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getType() == String.class) {
                if (StringUtils.isEmpty((String) field.get(origin))) {
                    field.set(origin, field.get(intactObject));
                }
            } else {
                if (field.get(origin) == null) {
                    field.set(origin, field.get(intactObject));
                }
            }
        }
    }
​
}

image-20220811144217760

image-20220811144223444

3. 单例模式的bug

反射可以调用私有的构造器造成的

写一个单例的类

/**
 * @Author: sc
 * @Date: 2022/8/11 10:26
 */
public class Person {
​
   private static Person instance;
​
   private Person(){
​
   }
​
   public static Person getInstance(){
       if(instance == null){
           instance = new Person();
       }
       return instance;
   }
}

再写一个测试类

/**
 * @Author: sc
 * @Date: 2022/8/11 10:32
 */
public class test {
​
    public static void main(String[] args) throws Exception {
        Person p1 = Person.getInstance();
        Person p2 = Person.getInstance();
        Person p3 = Person.getInstance();
​
        System.out.println(p1);
        System.out.println(p2);
        System.out.println(p3);
    }
​
}

可以看到这里一直是一个对象

image-20220811150641957

这里使用反射获取构造器再新建

/**
 * @Author: sc
 * @Date: 2022/8/11 10:32
 */
public class test {
​
    public static void main(String[] args) throws Exception {
        Person p1 = Person.getInstance();
        Person p2 = Person.getInstance();
        Person p3 = Person.getInstance();
​
        System.out.println(p1);
        System.out.println(p2);
        System.out.println(p3);
        Constructor<? extends Person> declareConstructor = p1.getClass().getDeclaredConstructor();
        declareConstructor.setAccessible(true);
        System.out.println(declareConstructor.newInstance());
    }
​
}

可以看到这里创建了另外一个对象

image-20220811150852696

那么应该怎么解决呢?

可以将私有构造函数加一点东西

private Person() throws Exception {
    if(instance == null){
        throw new Exception("实例已经存在,不允许再创建。。。");
    }
}

image-20220811151104464

就没问题了

4. 反射应用的场景

  1. jdbc封装
  2. SpringIOC
  3. JDBCTemplate
  4. Mybatis
  5. ......

应用很少,但是在底层框架的中很常用