最安全的單例模式-列舉
阿新 • • 發佈:2021-01-18
列舉實現
首先我們先看一下列舉實現單例模式
public enum EnumSingleton {
INSTANCE;
// 列舉能夠絕對有效的防止例項化多次,和防止反射和序列化破解
public void add() {
System.out.println("add方法...");
}
}
Test類
public class Test {
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1==instance2);
}
}
我們看一下輸出結果
列舉的安全性
1、上一篇部落格中提到了我們可以通過JAVA的反射機制去破解我們的單例模式
https://blog.csdn.net/weixin_45498245/article/details/112742030
2、我們可以用反射去破解一下列舉
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
Class<?> aClass = Class.forName("單例模式.列舉實現單例.EnumSingleton");
Constructor<?> declaredConstructor = aClass. getDeclaredConstructor();
declaredConstructor.setAccessible(true);//設定構造許可權為可以訪問
EnumSingleton enumSingleton= (EnumSingleton) declaredConstructor.newInstance();//Singleton2 with modifiers "private"
System.out.println(enumSingleton);
}
}
Exception in thread “main” java.lang.NoSuchMethodException: 單例模式.列舉實現單例.EnumSingleton.()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at 單例模式.破解單例.Test.main(Test.java:26)
這是為什麼呢?我們接著往下看列舉的原始碼分析
列舉原始碼分析
結果如下,這是為什麼呢 我們通過反編譯工具可以看一下列舉的程式碼(這是通過反編譯工具去反編譯了一下我們上邊的EnumSingleton.class)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingleton.java
package 53554F8B6A215F0F.679A4E3E5B9E73B053554F8B;
import java.io.PrintStream;
public final class EnumSingleton extends Enum//可以看出我們的列舉無非也是一個類去實現了一個Enum父類
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
//根據我們列舉中定義的名字去拿類
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(53554F8B6A215F0F/679A4E3E5B9E73B053554F8B/EnumSingleton, name);
}
//有參構造,這裡可以發現列舉是沒有預設的無參構造的 i是一個序號
private EnumSingleton(String s, int i)
{
super(s, i);
}
public void add()
{
System.out.println("add\u65B9\u6CD5...");
}
private static final EnumSingleton $VALUES[]; //存放我們物件的陣列,然後去初始化
//可以看到列舉的話是在靜態程式碼塊中去初始化的
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
看完原始碼之後那麼我們通過反射去測試載入有參構造(因為通過上邊我們看出來列舉是沒有無參構造的,只有一個有參構造,那麼我們就通過反射對有參構造下手)
//有參構造去破解列舉的單例
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
Class<?> aClass = Class.forName("單例模式.列舉實現單例.EnumSingleton");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class,int.class);//這次我們測試去破解有參構造
declaredConstructor.setAccessible(true);//設定構造許可權為可以訪問
EnumSingleton enumSingleton= (EnumSingleton) declaredConstructor.newInstance("123",1);//Singleton2 with modifiers "private"
System.out.println(enumSingleton);
}
}
結果如何呢?
Cannot reflectively create enum objects
會給我們報錯,意思是沒法去反射列舉類
為什麼呢?我們看一下反射中的newInstance原始碼:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// !!!這裡我們看到如果反射的類是列舉的話,會直接丟擲異常!!!
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
所以各位小夥伴明白為什麼列舉是最安全的了吧,反射機制的底層就規定了我們的列舉沒法進行反射獲取!