密封接口定义跨系统消息通知契约,核心是将“哪些消息体合法”固化到编译期:通过 sealed + permits 显式声明合法类型,配合模块封装、修饰符策略及序列化白名单校验,实现契约可验证、不可绕过。

用密封接口定义跨系统消息通知契约,核心是把“哪些消息体合法”这个业务规则提前固化到编译期,而不是靠文档约定或运行时校验。它让契约真正可验证、不可绕过。
用 sealed + permits 明确列出所有允许的消息体类型
消息通知契约通常表现为一个统一入口接口(如 Notification),不同业务场景对应不同消息体(如 OrderConfirmed、PaymentFailed、UserRegistered)。密封接口强制要求:
- 所有合法消息体必须在 permits 子句中显式写出,例如:
public sealed interface Notification permits OrderConfirmed, PaymentFailed, UserRegistered - 每个列出的类必须与接口在同一模块内,且不能是抽象类或未声明修饰符的普通类
- 外部系统若想发送新类型消息,必须向该模块提交代码变更并重新编译——这天然形成接入审批流程
为每个消息体选择合适的封闭策略
不是所有消息体都该被封死。根据扩展需求决定实现类的修饰符:
-
final:适用于结构稳定、绝不允许定制字段的消息,如
public final class OrderConfirmed implements Notification -
sealed:适用于需分层建模的场景,比如所有支付类消息共用父类型
PaymentEvent,再由PaymentSucceeded和PaymentRefunded继承 -
non-sealed:仅对极少数需保留现场扩展能力的类型开放,例如测试用的
MockNotification,但生产环境应避免
配合模块封装隐藏实现细节
仅靠密封性无法阻止反射或跨模块非法实例化。需在 module-info.java 中控制可见性:
- 只 exports 定义契约的包(如
com.example.notification),不导出具体消息体所在的包 - 不 opens 实现类包给其他模块,除非必要(如序列化框架需要)
- 对外 API 全部使用
Notification接口类型,禁止暴露OrderConfirmed等具体类名
与消息序列化/反序列化机制协同
密封接口本身不处理序列化,但能显著提升其安全性:
- 反序列化时,可基于
permits列表做白名单校验,拒绝未知类型字符串 - 结合 Jackson 或 Gson 的模块配置,只注册已知的实现类,避免自动发现带来的风险
- 配合 Java 21 的模式匹配 switch,能写出穷尽性检查的消费逻辑,编译器会提示是否漏处理某类消息











