Spring版本升级后Event发布者不等待跨项目监听器问题咨询
我在两个不同项目中有3个类,希望从一个项目发布事件后,等待另一个项目的监听器方法执行完毕。但目前发布者方法并未等待监听器,而是在发布者方法结束后监听器才开始运行。
最小可复现示例
项目1中的两个类
import org.springframework.context.ApplicationEvent; public class ConfigEvent extends ApplicationEvent { private static final long serialVersionUID = 144711L; public ConfigEvent(Object source) { super(source); } }
@Configuration public class Config { @Autowired ApplicationEventPublisher publisher; @Bean public Object object(Object unnecessaryObject) { System.out.println("Before publish"); publisher.publishEvent(new ConfigEvent(this)); System.out.println("After publish"); Object obj= new Object (); System.out.println("End of publisher"); return obj; } }
项目2中的类
@Component public class MyConfig implements ApplicationListener<ConfigEvent> { @Override public void onApplicationEvent(ConfigEvent event) { System.out.println("Start of Event listener"); System.out.println("End of Event listener"); } }
当前输出顺序
- Before publish
- After publish
- End of publisher
- Start of Event listener
- End of Event listener
预期输出顺序
- Before publish
- Start of Event listener
- End of Event listener
- After publish
- End of publisher
此前发布者方法会等待监听器执行,但升级Spring版本后不再等待。请问这是Spring Application Listener的正常工作机制还是存在Bug?如何实现预期的执行顺序?
这是正常机制还是Bug?
这不是Bug,是Spring版本升级后事件发布机制的正常变化。
在Spring 4.2及之前,默认的事件发布是同步阻塞的:调用publishEvent后,会立即触发所有匹配的监听器执行,直到所有监听器处理完成,publishEvent才会返回,发布者方法继续执行。
但从Spring 4.2开始,Spring引入了ApplicationEventMulticaster的异步支持,尤其是在Spring 5+版本中,如果项目启用了@EnableAsync,或者容器中存在自定义TaskExecutor Bean,默认的事件广播器会切换为异步模式,导致publishEvent调用后立即返回,监听器在后台线程执行。另外,如果事件发布发生在Spring容器初始化阶段(比如示例中在@Bean方法里发布事件),Spring会暂存事件,等到容器初始化完成后再批量触发,也会造成监听器延迟执行。
如何实现预期的同步阻塞执行?
可以通过以下几种方式确保事件发布是同步阻塞的,让发布者等待监听器执行完毕:
1. 显式配置同步的ApplicationEventMulticaster
在项目中添加同步事件广播器Bean,覆盖默认的异步配置:
@Configuration public class EventConfig { @Bean public ApplicationEventMulticaster applicationEventMulticaster() { SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(); // 不设置TaskExecutor,默认使用同步执行逻辑 return multicaster; } }
这个配置会强制所有事件发布都阻塞,直到监听器执行完成。
2. 为特定监听器禁用异步
如果项目全局启用了@EnableAsync,但希望这个监听器同步执行,直接移除监听器方法上的@Async注解即可:
@Component public class MyConfig implements ApplicationListener<ConfigEvent> { @Override // 移除@Async注解,确保同步执行 public void onApplicationEvent(ConfigEvent event) { System.out.println("Start of Event listener"); System.out.println("End of Event listener"); } }
3. 延迟事件发布到容器初始化完成后
如果事件是在@Bean方法中发布的,容器初始化阶段可能会暂存事件。可以将事件发布延迟到容器初始化完成后,比如使用@PostConstruct:
@Configuration public class Config { @Autowired ApplicationEventPublisher publisher; @Bean public Object object(Object unnecessaryObject) { return new Object(); } @PostConstruct public void publishEventAfterInit() { System.out.println("Before publish"); publisher.publishEvent(new ConfigEvent(this)); System.out.println("After publish"); System.out.println("End of publisher"); } }
这样事件会在所有Bean初始化完成后发布,同步监听器会立即被触发,发布者方法会等待其执行完毕。
内容的提问来源于stack exchange,提问作者Bedirhan Gökce

