反射能破坏绝大多数单例实现,但枚举单例天然免疫;防御核心是在构造器里加状态检查,同时配合readresolve()防序列化。

直接说结论:反射能破坏绝大多数单例实现,但枚举单例天然免疫;防御核心是在构造器里加状态检查,同时配合 readResolve() 防序列化。
反射为什么能破坏单例
Java 反射允许绕过访问控制,调用私有构造方法。哪怕构造器是 private,只要执行:
Constructor.setAccessible(true)constructor.newInstance()
就能生成新实例——此时单例的“唯一性”就失效了。常见实现(饿汉、懒汉、双重检查锁、静态内部类)都逃不过这一关,因为它们只靠“私有构造器”做防护,而反射能强行打开这扇门。
怎么防御反射攻击
关键是在私有构造器里加入实例存在性校验,一旦发现已有实例,就抛异常阻断创建:
- 声明一个
private static boolean initialized = false标志位 - 构造器开头判断:
if (initialized) throw new RuntimeException("单例已被初始化"); - 确认通过后才设为
true
注意:这个标志必须是 static,否则每个新对象都会有自己的副本,起不到全局拦截作用。另外,该检查要放在构造器最前面,防止构造中途被中断后状态不一致。
为什么枚举单例不用写防御代码
因为 JVM 对枚举类型做了硬性限制:
- 反射调用
Enum的私有构造器时,JVM 直接抛IllegalArgumentException - 反序列化时自动复用已有枚举常量,无需手动写
readResolve()
例如:public enum Singleton { INSTANCE; },获取实例只需 Singleton.INSTANCE,线程安全、简洁、零配置防反射防序列化。
别漏掉反序列化这个坑
如果单例类实现了 Serializable,反序列化会绕过构造器,生成新对象。必须显式提供:
-
private Object readResolve() { return INSTANCE; }(枚举除外) - 这个方法在反序列化完成后被自动调用,返回已有实例,覆盖新建对象
只防反射不防序列化,等于防了一半;两者缺一不可,尤其在分布式缓存或 RPC 场景下极易触发。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











