原来我们写的单例还存在缺陷~~

Tips:你的单例对象能被反射吗😊,能被反序列化吗😊,如果做不好防护,那么你的单例对象就不再是独一无二,而会多一个同胞兄弟了😄😄😄

01 单例模式简介

  • 单例模式(Singleton Pattern)是一种创建型设计模式,旨在确保某个类只有一个实例,并提供一个全局访问点。

02 常用单例模式

饿汉式(Eager Initialization)

java 复制代码
public class EagerSingleton implements Serializable {
    private static final EagerSingleton instance = new EagerSingleton();
    
    private EagerSingleton() {
        // 私有构造函数
    }
    
    public static EagerSingleton getInstance() {
        return instance;
    }
}

懒汉式(Lazy Initialization)

java 复制代码
public class LazySingleton implements Serializable {
    private static LazySingleton instance;
    
    private LazySingleton() {}
    
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

双重检查锁定(Double-Checked Locking)

java 复制代码
public class DoubleCheckedSingleton implements Serializable {
    private static volatile DoubleCheckedSingleton instance;
    
    private DoubleCheckedSingleton() {}
    
    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

静态内部类(Static Inner Class)

java 复制代码
public class InnerClassSingleton implements Serializable {
    private InnerClassSingleton() {}
    
    private static class SingletonHolder {
        private static final InnerClassSingleton instance = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

枚举(Enum)

java 复制代码
public enum EnumSingleton implements Serializable {
    INSTANCE;
    
    public void doSomething() {
        // 业务方法
    }
}

03 缺陷

  1. 无法应对反射、反序列化的考验~~
java 复制代码
public class MainTest {
    public static void main(String[] args) throws Exception {
        testReflectionAttack(LazySingleton.class, LazySingleton.getInstance());
        testReflectionAttack(EagerSingleton.class, EagerSingleton.getInstance());
        testReflectionAttack(DoubleCheckedSingleton.class, DoubleCheckedSingleton.getInstance());
        testReflectionAttack(InnerClassSingleton.class, InnerClassSingleton.getInstance());
        testReflectionAttack(EnumSingleton.class, EnumSingleton.INSTANCE);

        testSerializationAttack(LazySingleton.getInstance());
        testSerializationAttack(EagerSingleton.getInstance());
        testSerializationAttack(DoubleCheckedSingleton.getInstance());
        testSerializationAttack(InnerClassSingleton.getInstance());
        testSerializationAttack(EnumSingleton.INSTANCE);
    }

    /**
     * 测试反射攻击单例
     */
    public static <T> void testReflectionAttack(Class<T> singletonClass, T originalInstance) {
        System.out.println("\n=== 测试反射攻击: " + singletonClass.getSimpleName() + " ===");

        try {
            // 获取私有构造函数
            Constructor<T> constructor = singletonClass.getDeclaredConstructor();
            constructor.setAccessible(true); // 突破私有访问限制

            // 尝试通过反射创建新实例
            T reflectedInstance = constructor.newInstance();

            System.out.println("原始实例: " + originalInstance.hashCode());
            System.out.println("反射实例: " + reflectedInstance.hashCode());
            System.out.println("是否相同: " + (originalInstance == reflectedInstance));
            System.out.println("反射攻击: " + (originalInstance != reflectedInstance ? "成功 ✅" : "失败 ❌"));

        } catch (Exception e) {
            System.out.println("反射攻击失败: " + e.getMessage());
            System.out.println("反射攻击: 被防护 ❌"); 
        }
    }

    /**
     * 测试序列化/反序列化攻击
     */
    public static <T> void testSerializationAttack(T instance) {
        System.out.println("\n=== 测试序列化攻击: " + instance.getClass().getSimpleName() + " ===");

        try {
            // 模拟序列化和反序列化
            java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
            java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(baos);
            oos.writeObject(instance);
            oos.close();

            java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(baos.toByteArray());
            java.io.ObjectInputStream ois = new java.io.ObjectInputStream(bais);
            Object deserializedInstance = ois.readObject();
            ois.close();

            System.out.println("原始实例: " + instance.hashCode());
            System.out.println("反序列化实例: " + deserializedInstance.hashCode());
            System.out.println("是否相同: " + (instance == deserializedInstance));
            System.out.println("反序列化攻击: " +
                    (instance != deserializedInstance ? "成功 ✅" : "失败 ❌"));

        } catch (Exception e) {
            System.out.println("序列化测试异常: " + e.getMessage());
        }
    }
}

输出结果:

shell 复制代码
=== 测试反射攻击: LazySingleton ===
原始实例: 1029991479
反射实例: 670700378
是否相同: false
反射攻击: 成功 ✅

=== 测试反射攻击: EagerSingleton ===
原始实例: 728890494
反射实例: 1558600329
是否相同: false
反射攻击: 成功 ✅

=== 测试反射攻击: DoubleCheckedSingleton ===
原始实例: 445051633
反射实例: 1051754451
是否相同: false
反射攻击: 成功 ✅

=== 测试反射攻击: InnerClassSingleton ===
原始实例: 1147985808
反射实例: 2040495657
是否相同: false
反射攻击: 成功 ✅

=== 测试反射攻击: EnumSingleton ===
反射攻击失败: com.dhz.study.demo.design.singleton.EnumSingleton.<init>()
反射攻击: 被防护 ❌

=== 测试序列化攻击: LazySingleton ===
原始实例: 1029991479
反序列化实例: 920011586
是否相同: false
反序列化攻击: 成功 ✅

=== 测试序列化攻击: EagerSingleton ===
原始实例: 728890494
反序列化实例: 818403870
是否相同: false
反序列化攻击: 成功 ✅

=== 测试序列化攻击: DoubleCheckedSingleton ===
原始实例: 445051633
反序列化实例: 398887205
是否相同: false
反序列化攻击: 成功 ✅

=== 测试序列化攻击: InnerClassSingleton ===
原始实例: 1147985808
反序列化实例: 2047526627
是否相同: false
反序列化攻击: 成功 ✅

=== 测试序列化攻击: EnumSingleton ===
原始实例: 718231523
反序列化实例: 718231523
是否相同: true
反序列化攻击: 失败 ❌

安全性总结

实现方式 反射安全 反序列化安全 需要额外防护
饿汉式
懒汉式
双重检查
静态内部类
枚举

可以看到,除了枚举类型的单例,其他几种方式我们都可以通过反射和反序列化来创建新的实例使用,这显然是不合适的。

04 优化方案

对于反序列化而言,修复方式很简单,因为反序列化时,会调用对象的readResolve()方法,只要我们重写这个方法,就可以避免反序列化产生新的对象,以懒汉式实现的方式为例,以下是修复后的逻辑:

java 复制代码
public class LazySingleton implements Serializable {
    private static LazySingleton instance;
    
    private LazySingleton() {}
    
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    // 防止反序列化攻击
    private Object readResolve() {
        return instance;
    }
}

当我们再次调用反序列测试用例,可以看到单例对象和反序列化对象已经是同一个对象了(此处略去调用过程)。

而反射的情况就比较复杂了,有同学可能会说,可以定义一个常量,让构造方法只初始化一次不就可以了吗,类似于下面的情况:

java 复制代码
public class LazySingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    // 初始化常量,默认false,单例初始化后改为true
    private static boolean initialized = false;
    private static LazySingleton instance;

    private LazySingleton() {
        if (initialized) {
            throw new RuntimeException("单例模式禁止通过反射创建实例");
        }
        initialized = true;
    }

    public static synchronized LazySingleton getInstance() {
        if (instance != null) {
            return instance;
        }
        instance = new LazySingleton();
        return instance;
    }

    // 防止反序列化攻击
    private Object readResolve() {
        return instance;
    }
}

这里我们通过定义了常量initialized来控制构造方法只会初始化一次,当调用完getInstance()方法后,已经将initialized属性置为了true,再次通过反射来调用构造方法时,会抛异常出来,禁止调用。

但这也只是提高了反射的难度而已,并没有完全做到反射防护的情况,调用方完全可以在反射时先将initialized置为true,然后再调用构造方法,所以这种方案也不能完全解决以上问题。

那除了枚举就没有其他的方案来构造单例了吗,有的,下面我给大家推荐两种。

  • 基于类加载机制的防护
java 复制代码
public class InnerClassSingleton implements Serializable {

    private static class SingletonHolder {
        static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }

    private InnerClassSingleton() {
        if (SingletonHolder.INSTANCE != null) {
            throw new IllegalStateException("单例实例已经存在,禁止通过反射创建");
        }
    }
}
  • 使用 RuntimePermission 的安全管理器方案
java 复制代码
public final class SecurityManagerSingleton implements Serializable {
    private static volatile SecurityManagerSingleton instance;

    private SecurityManagerSingleton() {
        // 安全检查:检查是否有反射访问权限
        checkReflectionPermission();
    }

    public static SecurityManagerSingleton getInstance() {
        if (instance == null) {
            synchronized (SecurityManagerSingleton.class) {
                if (instance == null) {
                    instance = AccessController.doPrivileged(
                            (PrivilegedAction<SecurityManagerSingleton>) () -> new SecurityManagerSingleton()
                    );
                }
            }
        }
        return instance;
    }

    /**
     * 检查反射权限
     */
    private static void checkReflectionPermission() {
        SecurityManager securityManager = System.getSecurityManager();
        if (securityManager != null) {
            // 检查反射调用的权限
            securityManager.checkPermission(new RuntimePermission("accessDeclaredMembers"));
            securityManager.checkPermission(new RuntimePermission("reflectionFactoryAccess"));
        } else {
            // 如果没有安全管理器,则通过检查调用栈的方式来判断
            checkCallStack();
        }
    }

    /**
     * 检查调用栈
     */
    private static void checkCallStack() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

        // 检查调用栈中是否有反射相关的调用
        for (int i = 0; i < Math.min(stackTrace.length, 8); i++) {
            String className = stackTrace[i].getClassName();
            String methodName = stackTrace[i].getMethodName();

            if (className.startsWith("sun.reflect.") ||
                    className.startsWith("jdk.internal.reflect.") ||
                    methodName.equals("newInstance")) {
                throw new SecurityException("检测到反射调用,禁止创建单例实例");
            }
        }
    }
}

总结

当然了,我们平时的业务其实用不到这么复杂的实现逻辑,本文章只是单纯从技术的层面上和大家进行探讨,就当做技术交流,一般情况下使用枚举或其他几种单例就能够满足我们的业务需求。

码字不易,帮忙点赞收藏一下吧,感谢大家的支持! 😊😊😊

相关推荐
Tiny_React3 小时前
智能体设计模式-CH13:人类参与环节(Human-in-the-Loop)
设计模式
Tiny_React3 小时前
智能体设计模式-CH09:学习与适应(Learning and Adaptation)
设计模式
Tiny_React3 小时前
智能体设计模式-CH10:模型上下文协议(Model Context Protocol)
设计模式
Tiny_React3 小时前
智能体设计模式-CH11:目标设定与监控(Goal Setting and Monitoring)
设计模式
Deschen14 小时前
设计模式-外观模式
java·设计模式·外观模式
恋红尘20 小时前
设计模式详解
设计模式
Code_Geo1 天前
agent设计模式:第一章节—提示链
microsoft·设计模式·agent·模型
懂得节能嘛.1 天前
【设计模式】Java规则树重构复杂业务逻辑
java·开发语言·设计模式
tan77º1 天前
【项目】基于多设计模式下的同步&异步日志系统 - 项目介绍与前置知识
linux·c++·设计模式