单例模式与反射的博弈
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); // 对象比较
- 输出结果如下:
4. 单例模式在构造器中加入验证防止反射使用构造器
代码如下:
// 防止反射破坏
private static boolean flag = true;
// 1. 私有化构造器
private Singleton_1() {
if (flag) {
flag = false; // 在第一次构造完实例后将不能使用
} else {
throw new RuntimeException("单例模式遇到攻击,第二个对象未创建成功");
}
}
输出如下:
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));
}
}
- 输出结果
结果是直接报错,因为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));
}
}
- 输出结果
通过第二行异常点进去看到源码
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");
****省略****
}