Java封装需确保真只读:final+private+不可变返回值防逃逸;构造器一次性初始化;工厂/Builder控制创建;反序列化防护;运行时拦截反射;对外仅暴露只读接口。

Java 中的封装不是简单地把字段设为 private 就完事,而是要让“只读”真正不可篡改、可验证、且对外提供清晰可控的访问边界。关键在于:**限制修改入口 + 防止绕过手段 + 明确暴露意图**。
用 final + private + 不可变返回值实现真只读
这是最基础也最关键的一步。仅用 private 不够,字段必须声明为 final,且 getter 不能返回可变对象引用。
-
字段本身不可变:如
private final String name;或private final List<String> roles; -
避免逃逸(Escape):若字段是集合或数组,getter 必须返回不可修改视图,例如:
public List<String> getRoles() { return Collections.unmodifiableList(roles); }
或使用ImmutableList.copyOf(roles)(Guava) -
构造即确定:所有
final字段应在构造器中一次性赋值,不提供任何 setter 或内部修改逻辑
通过工厂或构建器控制初始化过程
防止对象在创建后被反射或序列化破坏只读性,应将构造逻辑收口。
- 使用静态工厂方法(如
Config.of(...))或 Builder 模式,隐藏构造器 - Builder 内部用私有构造器,且构建完成后清空可变中间状态,确保最终对象完全冻结
- 对敏感类可添加
readObject自定义反序列化,抛出InvalidClassException阻止非法反序列化
运行时防护:拦截反射与字节码篡改
即使封装得再好,反射仍可能绕过。生产环境需主动防御:
立即学习“Java免费学习笔记(深入)”;
- 在关键类的构造器或 getter 中检查调用栈,识别非预期来源(如非本包/非可信类调用)
- 启动时通过
System.setSecurityManager(Java 17 及以前)配合策略文件禁止ReflectPermission,但注意该机制已弃用 - 更现代的做法:用 Java Agent + Byte Buddy 在类加载期织入校验,拦截对
setAccessible(true)的调用,或直接修改字段访问标志 - 启用 JVM 参数
-XX:+EnableDynamicAgent(JDK 21+)配合可信 agent 实现运行时加固
对外暴露只读契约:接口比实现更重要
不要让外部依赖具体类,而应定义只读接口,强制契约约束。
- 例如定义
interface ReadOnlyConfig { String getAppName(); int getVersion(); } - 真实实现类实现该接口,但不暴露其可变方法或内部结构
- 框架注入或返回时统一用接口类型,连 IDE 提示和编译检查都帮你守住只读边界
- 配合模块系统(
module-info.java),只导出只读接口包,隐藏实现包


















