diff --git a/jad/EnumInstance.jad b/jad/EnumInstance.jad new file mode 100644 index 0000000..a7d267b --- /dev/null +++ b/jad/EnumInstance.jad @@ -0,0 +1,53 @@ +// 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: EnumInstance.java + +package top.fjy8018.designpattern.pattern.creational.singleton; + + +public final class EnumInstance extends Enum +{ + + public static EnumInstance[] values() + { + return (EnumInstance[])$VALUES.clone(); + } + + public static EnumInstance valueOf(String name) + { + return (EnumInstance)Enum.valueOf(top/fjy8018/designpattern/pattern/creational/singleton/EnumInstance, name); + } + + private EnumInstance(String s, int i) + { + super(s, i); + } + + public Object getData() + { + return data; + } + + public void setData(Object data) + { + this.data = data; + } + + public Object getInstance() + { + return INSTANCE; + } + + public static final EnumInstance INSTANCE; + private Object data; + private static final EnumInstance $VALUES[]; + + static + { + INSTANCE = new EnumInstance("INSTANCE", 0); + $VALUES = (new EnumInstance[] { + INSTANCE + }); + } +} diff --git a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/EnumInstance.java b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/EnumInstance.java new file mode 100644 index 0000000..61327bf --- /dev/null +++ b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/EnumInstance.java @@ -0,0 +1,38 @@ +package top.fjy8018.designpattern.pattern.creational.singleton; + +/** + * 枚举单例模式(effective Java推荐) + * 枚举类天生可序列化,有效防止序列化攻击 + *
+ * 通过反编译可知: + * 1. 枚举类的通过final修饰,不能被继承 + * 2. 其构造器私有,不能被调用,如果通过反射强制调用,在newInstance方法中会抛出异常 + * 3. 枚举变量声明为final和static + * 4. 枚举变量通过静态块实例化(类似饿汉式),所以线程安全 + * + * @author F嘉阳 + * @date 2018-09-24 20:30 + */ +public enum EnumInstance { + /** + * 单例枚举 + */ + INSTANCE; + + /** + * 目标实例化对象 + */ + private Object data; + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public Object getInstance() { + return INSTANCE; + } +} diff --git a/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonReflectAttack.java b/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonReflectAttack.java index 717314a..804a985 100644 --- a/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonReflectAttack.java +++ b/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonReflectAttack.java @@ -15,6 +15,37 @@ import static org.junit.jupiter.api.Assertions.*; @Slf4j class SingletonReflectAttack { + /** + * 枚举单例反射攻击 + * 枚举类只有一个构造器 {@link Enum#Enum(String, int)} + * 由于{@link Constructor#newInstance(Object...)} 416行指明枚举类不允许反射实例化,强制实例化会抛{@link IllegalArgumentException} + * 从底层保证了枚举类无法被反射攻击 + * + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws InstantiationException + */ + @Test + void enumInstanceGetInstance() throws Exception { + Class clazz = EnumInstance.class; + // 由于枚举类没有无参构造器,需要指定参数 + Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class); + + // 打开访问权限 + constructor.setAccessible(true); + // 通过反射获取实例 + EnumInstance reflectInstance = (EnumInstance) constructor.newInstance("fjy", 123); + // 通过正常方式获取 + EnumInstance instance = EnumInstance.INSTANCE; + + // 判断是否同一个对象 + log.info("反射方式:" + reflectInstance.getData()); + log.info("正常方式:" + instance.getData()); + // 此处结果为false,说明是两个不同的对象 + log.info(String.valueOf(reflectInstance.getData() == instance.getData())); + } + /** * 修复前懒汉式反射攻击 * 由于反射攻击足够强大,对于非类加载时实例化的单例模式,是无法彻底防止反射攻击的 @@ -96,7 +127,7 @@ class SingletonReflectAttack { // 通过反射获取实例 LazySingletonSynchronizedReflectImprove2 reflectInstance = (LazySingletonSynchronizedReflectImprove2) constructor.newInstance(); - + // 判断是否同一个对象 log.info("反射方式:" + reflectInstance); log.info("正常方式:" + instance); diff --git a/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java b/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java index 2bf8a60..da03945 100644 --- a/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java +++ b/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java @@ -31,6 +31,34 @@ class SingletonSerializableAttackTest { file.deleteOnExit(); } + /** + * 枚举类序列化攻击 + * 原因:{@link ObjectInputStream#readEnum(boolean)} 中对枚举的执行过程是2000 - 2007行 + * 通过枚举的类和名称来获取对象实例,因为枚举的名称在枚举中唯一,所以没有实例化的动作,保证了单例可靠性 + * + * @throws IOException + */ + @Test + void EnumInstanceGetInstance() throws Exception { + + EnumInstance singleton = EnumInstance.INSTANCE; + singleton.setData(new Object()); + + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME)); + oos.writeObject(singleton); + + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME)); + EnumInstance newInstance = (EnumInstance) ois.readObject(); + + log.info("写入前:" + singleton.getData()); + log.info("读出后:" + newInstance.getData()); + // 此处结果为false,说明是两个不同的对象 + log.info(String.valueOf(newInstance.getData() == singleton.getData())); + + oos.close(); + ois.close(); + } + /** * 通过序列化测试写入和读出后是否属于同一个对象 * 根据单例模式原则,永远只有一个实例化的对象