在Spring Modulith里,模块间通信往往依赖领域事件来降低耦合,但事件一旦变多,最容易出现两类问题:一是事件到底有没有发出去,二是监听器为什么看起来没反应。下面按你最常遇到的真实开发节奏,把验证链路和排查链路拆开讲清楚,照着做基本能把问题定位到发布端、事务边界、模块引导加载或事件发布登记表这四个点上。
一、领域事件在Spring Modulith里怎么验证
先把验证思路定下来:你要验证的不是某个类有没有被调用,而是一次业务刺激之后,系统是否发布了期望的事件,以及这些事件是否符合你关心的字段条件。Spring Modulith把这件事做成了测试层能力,你按模块写集成测试即可把事件链路锁住。
1、把测试能力加进来
在Maven或Gradle里加入spring-modulith-starter-test测试依赖,然后在IDE里触发依赖刷新,例如在IntelliJ的Maven窗口点击【Reload All Maven Projects】或在Gradle窗口点击【Reload All Gradle Projects】。依赖到位后,你才能在测试方法里直接拿到PublishedEvents或Scenario。
2、用模块级别的集成测试承载验证
把JUnit测试类放到某个应用模块包名下面或子包里,并使用 ApplicationModuleTest标注,这样启动上下文会默认只引导当前模块,测试范围天然贴合模块边界,避免全量启动带来噪音。若你把日志级别org.springframework.modulith调到DEBUG,就能看到它如何限制自动配置与组件扫描范围,帮助你确认测试到底跑了哪些模块。
3、直接验证已发布事件集合
在测试方法参数里声明PublishedEvents,执行你的业务入口后,用PublishedEvents按类型筛选,再用字段匹配去锁定目标事件数量与关键字段。若你已经在用AssertJ,直接把参数类型换成AssertablePublishedEvents,会更像写断言而不是写筛选器。
4、遇到异步与事务提交再用Scenario把链路跑通
当监听器采用异步并且在事务提交时执行时,普通断言很容易写得不稳定。此时用Scenario作为测试方法参数,从发布事件或调用某个Bean方法开始,接着声明你要等待的目标事件或状态变化,再触发到达并校验。Scenario会把刺激动作放进事务回调里,保证事件能交付给事务型监听器,但也意味着数据库改动不会自动回滚,需要你显式做清理。
二、Spring Modulith领域事件监听不触发怎么办
监听不触发通常不是框架没工作,而是你所选的监听模型需要满足前置条件,比如必须等事务提交、必须在正确模块被引导、必须是Spring管理的Bean。按下面顺序排查,基本能快速缩小范围。
1、先确认事件确实发布成功
发布端建议统一从ApplicationEventPublisher发布事件,优先把发布动作放在聚合根状态变更之后,这样语义更清晰,也更容易定位时序问题。默认事件发布是同步的,所以发布端执行路径能跑到publishEvent,就说明事件进入了Spring事件机制。
2、确认你用的是否是事务型监听模型
如果你用的是 TransactionalEventListener或 ApplicationModuleListener,这类监听器核心特点是依赖事务语义,常见触发点在事务提交阶段。换句话说,发布事件的业务方法如果没有开启事务,或事务最终回滚,监听器就会看起来没触发。 ApplicationModuleListener本质上也是为模块集成场景提供的快捷写法,通常与事务型事件监听语义绑定。
3、排除模块引导范围导致的监听器缺席
你在 ApplicationModuleTest里默认是STANDALONE,只启动当前模块。若监听器在另一个模块里,即使代码写对了也不会触发,因为那个Bean根本没被加载。解决方式是把测试模式切到DIRECT_DEPENDENCIES或ALL_DEPENDENCIES,或把测试类放到包含监听器的模块包下,让引导范围覆盖到监听器所在模块。
4、排查异步执行链路是否真正启动
文档里的典型做法会给监听方法加 Async,让它在提交后异步执行。此时你需要确认应用已启用异步执行,并且线程池没有被耗尽。定位时可以先临时去掉 Async让监听同步跑通,再把异步加回去做性能与可靠性调优。
5、如果用到事件发布登记表就直接查未完成记录
当你采用事务型监听并引入事件发布登记表时,框架会在事件发布时为每个事务型监听器写入一条发布记录,监听成功则标记完成,失败则保留为未完成,便于后续重试。你可以用IncompleteEventPublications和CompletedEventPublications这类API查看与管理记录,快速判断到底是没交付、交付失败还是已完成但你没看到效果。
三、Spring Modulith事件发布登记表怎么定位
当问题卡在事务提交、重试、跨模块二次发布时,最有效的抓手就是事件发布登记表和它的重放能力。你要做的是把问题从感觉层面变成可观察证据:有没有写入记录,哪个监听器对应的记录未完成,重放后是否能恢复。
1、用登记表判断交付阶段卡在哪里
事件发布登记表会在发布时识别出将收到事件的事务型监听器,并为每个监听器写入日志条目,监听执行成功则完成标记,失败则保留未完成供你部署重试机制。你排查时优先看未完成条目对应的监听器是哪一个,再回到该监听器的业务逻辑与资源依赖上去定位真实异常点。
2、用重放把偶发问题变成可复现问题
如果现场出现过重启后监听器才触发的现象,可以开启属性spring.modulith.events.republish-outstanding-events-on-restart,让应用启动时自动重放未完成事件。它能帮你判断是监听执行偶发失败,还是提交时序与二次发布造成的交付缺口。
3、给监听器单独事务边界避免互相拖累
当你希望监听器自己在新事务里跑完并独立提交,可以在监听方法上增加 Transactional并把传播行为设为REQUIRES_NEW,再配合事务型监听注解使用。这样做常用于监听逻辑需要写库、发消息且不希望影响原始业务事务的场景,也能减少提交阶段挤压导致的失败面。
4、注意监听器内部再发布事件的时序陷阱
社区有人反馈过一种场景:在一个TransactionalEventListener里处理事件并再发布第二个事件,而第三个模块用ApplicationModuleListener监听第二个事件时,可能出现监听不触发但重启重放后又触发的现象。遇到这种链路,优先把二次事件发布移动到明确的事务边界内,例如由业务服务在同一事务里发布,或在新事务里发布,并用Scenario写一条端到端用例把预期固定下来。
总结
在Spring Modulith里验证领域事件,优先把验证写进 ApplicationModuleTest,用PublishedEvents锁定事件集合,用Scenario兜住事务提交与异步交付的不确定性。监听不触发时,先从事务语义与模块引导范围下手,再借助事件发布登记表把交付过程可视化,最后针对二次发布与重放场景做边界收敛,你会发现大多数问题都能在这条链路上被稳定复现并修正。