1.反射
反射:在程序运行期间,可以通过反射动态的访问Java对象的属性、方法、构造方法等
下面我将以单例模式-静态内部类的方式来演示反射对单例的破坏
2.实现代码
public class Singleton {
private Singleton() {
}
private static class SingletonHandler {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
}
上面是单例模式-静态内部类的实现代码,没有学过单例模式-静态内部类的朋友,建议先看完6.Java设计模式-创建型模式-单例模式-静态内部类,这样更利于大家对下面讲解的理解。
public class TestDemo {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println("s1 == s2: " + (s1 == s2)); // s1 == s2: true
}
}
上面是我们测试的代码,通过Singleton.getInstance
获取到的是同一个对象,没有问题,符合单例的特性。
下面我们要开始搞破坏了呦~
import java.lang.reflect.Constructor;
public class TestDemo {
public static void main(String[] args) throws Exception{
Class<Singleton> clazz = Singleton.class; // 获取到Singleton的class对象
Constructor<Singleton> constructor = clazz.getDeclaredConstructor(); // 根据class对象获取到无参构造方法
constructor.setAccessible(true); // 由于无参构造方法是private的,所以要设置为可访问
Singleton instance1 = constructor.newInstance(); // 通过反射创建对象
Singleton instance2 = constructor.newInstance(); // 通过反射创建对象
System.out.println(instance1 == instance2); // false
}
}
上面是破坏单例的实现代码,建议大家先把注释的内容看完,再来看下面的讲解。
根据打印结果为false
,我们发现,instance1
和instance2
不是同一个对象,即单例遭到了破坏,即Singleton
的实例不是全局唯一的。
发现问题后,该怎么避免呢?
大家想想,在反射的时候,我们调用的是什么方法创建的对象?答案:我们调用的是Singleton
的构造方法来创建的对象。
既然反射能随心所欲的操作构造方法,那我们只能在构造方法内部做些文章了。
在原先代码的基础上,我只加了上图框起来的代码。
如果有人通过反射来调用构造方法,走到if
条件语句的时候,会有两种情况
- 静态成员变量
INSTANCE
还没有被创建,那么就会触发静态内部类SingletonHandler
的加载,这样子JVM就会帮我们完成INSTANCE
的创建,完成创建后,SingletonHandler.INSTANCE
就不为null
了,即就会抛上图所示的异常 - 静态成员变量
INSTANCE
已经被创建,由于SingletonHandler.INSTANCE
不为null
,所以就会抛上图所示的异常
大家发现没有,无论INSTANCE
有没有被创建,都会抛异常,并且如果INSTANCE
没有被创建,还会帮我们完成创建,这样子,不就避免了反射对单例的破坏了么。
3.总结
大家可以就单例模式-饿汉式、单例模式-懒汉式-“synchronized加锁“实现、单例模式-懒汉式-“双重检查锁+volatile“实现,自己来实现一下如何避免反射对单例的破坏,方法和我上面讲的类似,只是实现细节上有些许不同。
由于java
底层源码不允许通过反射来创建枚举对象,所以就不存在反射对枚举单例的影响了,那大家可能就有疑问了,java
底层源码是怎么不允许的呢?
大家在idea
里面全局搜Enum
这个类,会发现它是一个抽象类,它只有一个构造方法,且是两个参数的构造方法,第一个参数是String
类型的name
,第二个参数是int
类型的ordinal
,所以我用下图所示的代码来测一下反射,看看会发生什么。
大家会发现运行时抛了Cannot reflectively create enum objects
的异常,源码如上图,相信很好理解。