密封类限制子类是为了在编译期锁死敏感业务执行器类型,仅允许BankTransferExecutor、AlipayExecutor、WechatExecutor等预审通过的实现,新增需改接口、加类、走评审,杜绝绕过风控。

用密封类限制子类,不是为了“拦住别人写代码”,而是把敏感业务流程的执行器类型在编译期就锁死为明确、有限、可审计的几个实现——新增一个执行器,必须改接口、加类、走评审,不能悄悄塞进一个新类就绕过风控。
只允许预审通过的执行器实现密封接口
定义一个密封接口作为执行契约,比如 PaymentExecutor,它代表“能真正扣款”的能力:
- 用 public sealed interface PaymentExecutor permits BankTransferExecutor, AlipayExecutor, WechatExecutor 明确列出全部合法执行器
- 每个 permits 中的类必须在同一个模块内,且自身必须声明修饰符:BankTransferExecutor 声明为 final(银行通道不可扩展),AlipayExecutor 声明为 sealed(未来可能细分境内/跨境),WechatExecutor 声明为 non-sealed(仅限测试环境 Mock 类继承)
- 任何未出现在 permits 列表里的类,哪怕加了 implements PaymentExecutor,编译直接失败
执行器内部逻辑强制封装,禁止运行时篡改
光限制实现类还不够,得防住“合法类里藏非法行为”:
- 所有 final 执行器类中,核心方法(如 execute(ChargeRequest req))不提供 public 可重写入口;若需定制,走策略参数而非继承
- 把执行器工厂设为 package-private 或 module-private:只导出 PaymentExecutor 接口,不导出任何具体实现类所在的包
- 在 module-info.java 中不 exports 实现类包,只 exports 接口包;必要时用 opens to 仅对测试模块开放反射权限
与 switch 模式匹配联动,堵死漏判逻辑
在审批、记账、通知等关键环节,用 switch 处理执行器类型:
- 写 switch (executor) { case BankTransferExecutor b -> {...}; case AlipayExecutor a -> {...}; case WechatExecutor w -> {...}; }
- 此时编译器会校验是否覆盖全部 permits 子类;一旦新增 PaypalExecutor,所有未更新的 switch 立即报错 “not exhaustive”
- 禁用 instanceof + if/else 风格判断——它不会触发穷尽检查,容易遗漏新类型或误入 default 分支导致静默失败
上线前自动卡点:构建脚本验证密封完整性
把密封约束变成 CI 的硬性门禁:
- 用 Javac Task 的 -Xlint:sealed 参数开启密封性专项检查
- 编写 Gradle 任务扫描所有 sealed 接口/类,确认其 permits 列表中的每个类都存在、可访问、修饰符合规
- 结合 SonarQube 自定义规则,禁止在敏感模块中出现未被 permits 覆盖的 implements 或 extends 语句










