單例模式與反射的博弈
1. 單例模式介紹
單例模式的核心概念是:私有化構(gòu)造器扳埂,私有化靜態(tài)對(duì)象屬性业簿,對(duì)外公開(kāi)獲取對(duì)象屬性的方法,
從而使得外部類引用該類時(shí)阳懂,只存在唯一的一個(gè)對(duì)象梅尤。
2. 餓漢式單例模式代碼
- 餓漢式是最簡(jiǎn)單的一種實(shí)現(xiàn)方式,但是失去了 lazy loading (懶加載)的特性岩调,被 final 和 static 同時(shí)修飾的屬性會(huì)在類的準(zhǔn)備階段完成賦值
public class Singleton_1 {
// 1. 私有化構(gòu)造器
private Singleton_1() {}
// 2. 本類內(nèi)部創(chuàng)建靜態(tài)常量型實(shí)例
private final static Singleton_1 instance = new Singleton_1();
// 3. 對(duì)外提供公有的獲取實(shí)例方法
public static Singleton_1 getInstance() {
return instance;
}
}
3. 使用反射獲取私有化構(gòu)造器破解單例
// 正常方式獲得的對(duì)象
Singleton_1 instance = Singleton_1.getInstance();
// 獲得class 對(duì)象
Class<? extends Singleton_1> singleton_class = instance.getClass();
Constructor<? extends Singleton_1> constructor = singleton_class.getDeclaredConstructor(); // 獲取無(wú)參構(gòu)造器
constructor.setAccessible(true); // 給予私有構(gòu)造器的使用權(quán)限
// 使用構(gòu)造器創(chuàng)建新的實(shí)例
Singleton_1 singleton_1 = constructor.newInstance();
System.out.println("創(chuàng)建新實(shí)例成功巷燥,破解成功...");
System.out.println(instance.hashCode());
System.out.println(singleton_1.hashCode());
System.out.println(instance == singleton_1); // 對(duì)象比較
- 輸出結(jié)果如下:
4. 單例模式在構(gòu)造器中加入驗(yàn)證防止反射使用構(gòu)造器
代碼如下:
// 防止反射破壞
private static boolean flag = true;
// 1. 私有化構(gòu)造器
private Singleton_1() {
if (flag) {
flag = false; // 在第一次構(gòu)造完實(shí)例后將不能使用
} else {
throw new RuntimeException("單例模式遇到攻擊,第二個(gè)對(duì)象未創(chuàng)建成功");
}
}
輸出如下:
5. 反射修改flag驗(yàn)證
代碼如下:
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(枚舉)獨(dú)有的一些特性讓反射不能夠使用私有構(gòu)造器去創(chuàng)建新的實(shí)例,因此推薦使用Enum來(lái)設(shè)計(jì)單例模式
6. Enum單例
- 簡(jiǎn)潔好用
public enum SingletonEnum {
INSTANCE;
public void sayOK() {
System.out.println("ok~");
}
}
- 測(cè)試代碼
class Test_Enum {
public static void main(String[] args) throws Exception{
SingletonEnum instance_1 = SingletonEnum.INSTANCE;
SingletonEnum instance_2 = SingletonEnum.INSTANCE;
System.out.println("正常情況下葱淳,兩個(gè)對(duì)象是否相同? " + (instance_1 == instance_2));
// 使用反射
Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonEnum newInstance = constructor.newInstance();
System.out.println("使用反射钝腺,能否創(chuàng)建不同實(shí)例?" + (instance_1 == newInstance));
}
}
- 輸出結(jié)果
結(jié)果是直接報(bào)錯(cuò),因?yàn)镋num(枚舉)并沒(méi)有無(wú)參構(gòu)造器~ 不信就看下面代碼
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;
}
定義枚舉相當(dāng)于自動(dòng)繼承了Enum類赞厕,而Enum類本身只有一個(gè)構(gòu)造器艳狐,那就是 protected Enum(String name, int ordinal), 所以我們獲取不到無(wú)參構(gòu)造器。
那么用有參構(gòu)造器會(huì)怎么樣皿桑?
class Test_Enum {
public static void main(String[] args) throws Exception{
SingletonEnum instance_1 = SingletonEnum.INSTANCE;
SingletonEnum instance_2 = SingletonEnum.INSTANCE;
System.out.println("正常情況下僵驰,兩個(gè)對(duì)象是否相同? " + (instance_1 == instance_2));
// 拿enum定義的唯一的構(gòu)造器
Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
SingletonEnum newInstance = constructor.newInstance("Test1", 11);
System.out.println("使用反射,能否創(chuàng)建不同實(shí)例?" + (instance_1 == newInstance));
}
}
- 輸出結(jié)果
通過(guò)第二行異常點(diǎn)進(jìn)去看到源碼
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");
****省略****
}