在大型Java项目里,我们常看到用自定义注解、AOP切面或大量if语句做权限校验,还用反射去读本不该暴露的字段。其实从Java 9开始引入的模块化系统,已经通过包可见性提供了更干净的替代方案。利用模块声明文件,我们可以直接决定哪些包能被外部读取,其余全部锁在模块内。

为什么复杂校验逻辑不该是第一选择
很多老代码为了保护核心变量,会在每个方法前加身份判断:
public class OrderService {
public void cancelOrder(long id, User u) {
if (u == null || !u.hasRole("admin")) {
throw new SecurityException("no permission");
}
// 真正逻辑
}
}
这种写法把业务和安全搅在一起。如果模块边界清晰,外部根本拿不到内部包,也就不需要这类判断。
用module-info.java控制包可见性
模块化后,只有被exports的包才能被其他模块访问。未导出的包里的类和变量,在编译期和运行期都对外部不可见。
module com.shop.core {
exports com.shop.api;
// com.shop.internal 不导出,外部无法访问
}
此时com.shop.internal中的类即使不是private,别的模块也编译不过:
package com.shop.internal;
public class PriceRule {
public static int calc(int base) {
return base * 8 / 10;
}
}
替代变量保护与校验的实战结构
把对外接口放api包,把带敏感变量的实现放internal包,外部模块只能依赖api。
- api包:只放接口和DTO,全部exports
- internal包:放实现类与字段,不exports
- 调用方:只能使用api,无法直接new实现类或读字段
对比改造前后
| 方式 | 变量保护 | 权限校验 | 维护成本 |
|---|---|---|---|
| 传统反射+注解 | 弱,靠约定 | 散落各方法 | 高 |
| 模块包可见性 | 强,编译拦截 | 无需写 | 低 |
注意事项
如果必须用反射,可在module-info中开放指定包:
module com.shop.core {
exports com.shop.api;
opens com.shop.internal to com.shop.test;
}
这样测试模块能反射,生产模块仍不可见。通过合理划分exports与opens,我们用语言级机制替掉了手写的校验与保护代码。