diff --git a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonReflectImprove.java b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonReflectImprove.java new file mode 100644 index 0000000..f72d934 --- /dev/null +++ b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonReflectImprove.java @@ -0,0 +1,47 @@ +package top.fjy8018.designpattern.pattern.creational.singleton; + +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; + +/** + * 单例模式:饿汉式 + * 反射攻击改进 + * 对于在类加载时进行初始化的单例,可以用该方法解决反射攻击问题 + * + * @author F嘉阳 + * @date 2018-09-24 15:38 + */ +@Slf4j +public class HungrySingletonReflectImprove implements Serializable { + + private static final HungrySingletonReflectImprove HUNGRYSINGLETON; + + static { + log.debug(HungrySingletonReflectImprove.class.getSimpleName() + "静态块实例化"); + HUNGRYSINGLETON = new HungrySingletonReflectImprove(); + } + + private HungrySingletonReflectImprove() { + if (HUNGRYSINGLETON != null) { + throw new RuntimeException("单例模式禁止反射调用"); + } + log.debug(HungrySingletonReflectImprove.class.getSimpleName() + "构造器实例化"); + } + + public static HungrySingletonReflectImprove getInstance() { + return HUNGRYSINGLETON; + } + + /** + * 解决序列化攻击问题 + * + * @return + */ + private Object readResolve() { + log.debug("序列化获取对象"); + return HUNGRYSINGLETON; + } + + +} diff --git a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java index fb962fc..2b7652c 100644 --- a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java +++ b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java @@ -27,12 +27,12 @@ public class HungrySingletonSerializableImprove implements Serializable { private static final HungrySingletonSerializableImprove HUNGRYSINGLETON; static { - log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "实例化"); + log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "静态块实例化"); HUNGRYSINGLETON = new HungrySingletonSerializableImprove(); } private HungrySingletonSerializableImprove() { - log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "实例化"); + log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "构造器实例化"); } public static HungrySingletonSerializableImprove getInstance() { diff --git a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/StaticInnerClassSingleton.java b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/StaticInnerClassSingleton.java index 6001b8f..af80cfc 100644 --- a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/StaticInnerClassSingleton.java +++ b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/StaticInnerClassSingleton.java @@ -16,6 +16,7 @@ public class StaticInnerClassSingleton { * 私有构造器是单例必须的 */ private StaticInnerClassSingleton() { + log.debug(StaticInnerClassSingleton.class.getSimpleName() + "构造器实例化"); } /** @@ -24,6 +25,7 @@ public class StaticInnerClassSingleton { * @return */ public static StaticInnerClassSingleton getInstance() { + log.debug(StaticInnerClassSingleton.class.getSimpleName() + "内部类实例化"); return InnerClass.staticInnerClassSingleton; } diff --git a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/StaticInnerClassSingletonReflectImprove.java b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/StaticInnerClassSingletonReflectImprove.java new file mode 100644 index 0000000..58e63a6 --- /dev/null +++ b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/StaticInnerClassSingletonReflectImprove.java @@ -0,0 +1,70 @@ +package top.fjy8018.designpattern.pattern.creational.singleton; + + +import lombok.extern.slf4j.Slf4j; + +/** + * 懒汉单例:静态内部类实现(线程安全) + * 反射攻击改进 + * + * @author F嘉阳 + * @date 2018-09-24 13:11 + */ +@Slf4j +public class StaticInnerClassSingletonReflectImprove { + /** + * 私有构造器是单例必须的 + */ + private StaticInnerClassSingletonReflectImprove() { + if (InnerClass.staticInnerClassSingleton != null) { + throw new RuntimeException("单例模式禁止反射调用"); + } + log.debug(StaticInnerClassSingletonReflectImprove.class.getSimpleName() + "构造器实例化"); + } + + /** + * 开发单例入口 + * + * @return + */ + public static StaticInnerClassSingletonReflectImprove getInstance() { + log.debug(StaticInnerClassSingletonReflectImprove.class.getSimpleName() + "内部类实例化"); + return InnerClass.staticInnerClassSingleton; + } + + /** + * 静态内部类 + *
+ * 原理: + * JVM在类初始化时会加锁,使用初始化锁可以同步多个线程对一个类的初始化,确保了线程安全 + * 类初始化时机 + * 一. 主动引用 + * 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生): + * 1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。 + * 最常见的生成这 4 条指令的场景是: + * (1) 使用 new 关键字实例化对象的时候; + * (2) 读取或赋值一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候; + * (3) 以及调用一个类的静态方法的时候。 + * (4) 使用一个类的非常量静态成员 + * (5) 若一个类是顶级类,且该类有嵌套的断言语句(少见) + *
+ * 2. 使用 {@link java.lang.reflect} 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。 + * 3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 + * 4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类; + * 5. 当使用 JDK 1.7 的动态语言支持时,如果一个 {@link java.lang.invoke.MethodHandle} + * 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化; + *
+ * 二. 被动引用 + * 以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括: + *
+ * 1. 通过子类引用父类的静态字段,不会导致子类初始化。 + * System.out.println(SubClass.value); // value 字段在 SuperClass 中定义 + * 2. 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 {@link Object} 的子类,其中包含了数组的属性和方法。 + * SuperClass[] sca = new SuperClass[10]; + * 3. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。 + * System.out.println(ConstClass.HELLOWORLD); + */ + private static class InnerClass { + private static StaticInnerClassSingletonReflectImprove staticInnerClassSingleton = new StaticInnerClassSingletonReflectImprove(); + } +} 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 new file mode 100644 index 0000000..b7d8a99 --- /dev/null +++ b/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonReflectAttack.java @@ -0,0 +1,118 @@ +package top.fjy8018.designpattern.pattern.creational.singleton; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 单例模式反射攻击 + */ +@Slf4j +class SingletonReflectAttack { + + /** + * 修复前饿汉式攻击 + * + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws InstantiationException + */ + @Test + void HungrySingletongetInstanceBefore() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + Class clazz = HungrySingletonSerializableImprove.class; + Constructor constructor = clazz.getDeclaredConstructor(); + // 打开访问权限 + constructor.setAccessible(true); + // 通过反射获取实例 + HungrySingletonSerializableImprove reflectInstance = (HungrySingletonSerializableImprove) constructor.newInstance(); + // 通过正常方式获取 + HungrySingletonSerializableImprove instance = HungrySingletonSerializableImprove.getInstance(); + + // 判断是否同一个对象 + log.info("反射方式:" + reflectInstance); + log.info("正常方式:" + instance); + // 此处结果为false,说明是两个不同的对象 + log.info(String.valueOf(reflectInstance == instance)); + } + + /** + * 修复后饿汉式攻击 + * + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws InstantiationException + */ + @Test + void HungrySingletongetInstanceAfter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + Class clazz = HungrySingletonReflectImprove.class; + Constructor constructor = clazz.getDeclaredConstructor(); + // 打开访问权限 + constructor.setAccessible(true); + // 通过反射获取实例,此处抛出异常 + HungrySingletonReflectImprove reflectInstance = (HungrySingletonReflectImprove) constructor.newInstance(); + // 通过正常方式获取 + HungrySingletonReflectImprove instance = HungrySingletonReflectImprove.getInstance(); + + // 判断是否同一个对象 + log.info("反射方式:" + reflectInstance); + log.info("正常方式:" + instance); + log.info(String.valueOf(reflectInstance == instance)); + } + + /** + * 修复前懒汉式攻击 + * + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws InstantiationException + */ + @Test + void StaticInnerClassSingletongetInstanceBefore() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + Class clazz = StaticInnerClassSingleton.class; + Constructor constructor = clazz.getDeclaredConstructor(); + // 打开访问权限 + constructor.setAccessible(true); + // 通过反射获取实例 + StaticInnerClassSingleton reflectInstance = (StaticInnerClassSingleton) constructor.newInstance(); + // 通过正常方式获取 + StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance(); + + // 判断是否同一个对象 + log.info("反射方式:" + reflectInstance); + log.info("正常方式:" + instance); + // 此处结果为false,说明是两个不同的对象 + log.info(String.valueOf(reflectInstance == instance)); + } + + /** + * 修复后懒汉式攻击 + * + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws InstantiationException + */ + @Test + void StaticInnerClassSingletongetInstanceAfter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + Class clazz = StaticInnerClassSingletonReflectImprove.class; + Constructor constructor = clazz.getDeclaredConstructor(); + // 打开访问权限 + constructor.setAccessible(true); + // 通过反射获取实例 + StaticInnerClassSingletonReflectImprove reflectInstance = (StaticInnerClassSingletonReflectImprove) constructor.newInstance(); + // 通过正常方式获取 + StaticInnerClassSingletonReflectImprove instance = StaticInnerClassSingletonReflectImprove.getInstance(); + + // 判断是否同一个对象 + log.info("反射方式:" + reflectInstance); + log.info("正常方式:" + instance); + log.info(String.valueOf(reflectInstance == instance)); + } +} \ No newline at end of file