Spring 订阅发布模式(事件驱动模型) Spring 订阅发布模式基于 观察者设计模式,通过 ApplicationEvent (事件)、 ApplicationListener (监听器/订阅者)、 ApplicationEventPublisher (发布者)三者协同实现,核心用于解耦组件间通信,适用于业务逻辑分离(如操作日志记录、异步通知、状态变更回调等场景)。 一、核心组件与原理
- 核心角色
- 事件(Event):继承 ApplicationEvent 的实体类,封装需要传递的消息数据(如用户注册事件包含用户ID、注册时间等)。
- 发布者(Publisher):通过 ApplicationEventPublisher 接口发布事件,Spring 容器中所有 Bean 可直接注入该接口(或通过 ApplicationContext 间接调用,因 ApplicationContext 继承了 ApplicationEventPublisher )。
- 订阅者(Listener):实现 ApplicationListener 接口或通过 @EventListener 注解声明,用于监听指定事件并执行回调逻辑。
- 核心原理 1. 发布者调用 publishEvent() 方法发布事件; 2. Spring 容器( AbstractApplicationContext )接收事件后,遍历所有匹配该事件的订阅者; 3. 触发订阅者的回调方法(同步/异步执行),完成消息传递。 二、使用方式(3种常用实现) 方式1:基于注解(@EventListener,推荐) 最简洁的实现方式,无需实现接口,通过注解指定监听的事件类型。 步骤1:定义事件(继承 ApplicationEvent) java // 用户注册事件 public class UserRegisterEvent extends ApplicationEvent { // 事件携带的数据 private Long userId; private String username; private LocalDateTime registerTime;
// 构造方法必须调用父类构造器(传入事件源) public UserRegisterEvent(Object source, Long userId, String username) { super(source); this.userId = userId; this.username = username; this.registerTime = LocalDateTime.now(); }
// getter/setter 省略 } 步骤2:实现发布者(注入 ApplicationEventPublisher) java @Service public class UserService { // 注入发布者接口(Spring 自动注入实现类) @Autowired private ApplicationEventPublisher eventPublisher;
// 业务方法:用户注册后发布事件 public void register(String username) { // 1. 核心业务逻辑(如保存用户到数据库) Long userId = 1001L; // 模拟数据库生成的用户ID System.out.println("核心业务:用户[" + username + "]注册成功,ID=" + userId);
// 2. 发布事件(传入事件实例,source 通常为当前类this) eventPublisher.publishEvent(new UserRegisterEvent(this, userId, username)); } } 步骤3:实现订阅者(@EventListener 注解) java @Service public class UserRegisterListener { // 注解指定监听的事件类型(方法参数为事件实例) @EventListener(UserRegisterEvent.class) public void handleUserRegisterEvent(UserRegisterEvent event) { // 订阅者逻辑(如发送欢迎短信、记录操作日志) System.out.println("监听事件:用户注册成功"); System.out.println("用户ID:" + event.getUserId()); System.out.println("用户名:" + event.getUsername()); System.out.println("注册时间:" + event.getRegisterTime()); // 模拟发送短信:sendSms(event.getUsername()); } } 方式2:基于接口(ApplicationListener) 传统实现方式,订阅者需实现 ApplicationListener 接口,指定泛型为目标事件类型。 java @Service // 泛型指定监听 UserRegisterEvent 事件 public class UserRegisterInterfaceListener implements ApplicationListener {
// 重写 onApplicationEvent 方法,触发事件时执行 @Override public void onApplicationEvent(UserRegisterEvent event) { System.out.println("接口监听:用户[" + event.getUsername() + "]注册,执行日志记录"); } } 方式3:异步订阅(@Async + @EventListener) 默认情况下,订阅者回调是 同步执行(发布者需等待订阅者完成),若需异步执行(不阻塞发布者),可结合 Spring 异步注解 @Async 。 步骤1:开启异步支持(主启动类加注解) java @SpringBootApplication @EnableAsync // 开启 Spring 异步功能 public class SpringEventDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringEventDemoApplication.class, args); } } 步骤2:订阅者方法加 @Async 注解 java @Service public class AsyncUserRegisterListener {
// 结合 @Async 实现异步监听 @Async @EventListener(UserRegisterEvent.class) public void asyncHandleEvent(UserRegisterEvent event) { // 模拟耗时操作(如调用第三方短信接口) try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("异步监听:向用户[" + event.getUsername() + "]发送欢迎短信(异步执行,不阻塞主流程)"); } } 三、关键特性与注意事项
- 事件传播机制
- 默认同步:发布者调用 publishEvent() 后,会阻塞直到所有订阅者执行完成;
- 异步支持:通过 @Async 实现,需配合 @EnableAsync ,订阅者在独立线程执行;
- 事件继承:若事件 B 继承事件 A,监听事件 A 的订阅者会同时接收事件 A 和 B(多态特性)。
- 顺序执行控制 若多个订阅者监听同一事件,可通过 @Order 注解指定执行顺序(数值越小,优先级越高): java @Order(1) // 第一个执行 @EventListener(UserRegisterEvent.class) public void handle1(UserRegisterEvent event) { ... }
@Order(2) // 第二个执行 @EventListener(UserRegisterEvent.class) public void handle2(UserRegisterEvent event) { ... } 3. 注意事项
- 事件源(source):事件构造器需传入 source (事件触发者),通常为发布者自身( this ),便于订阅者追溯事件来源;
- 异常处理:同步执行时,订阅者抛出的未捕获异常会中断发布者流程;异步执行时,异常需在订阅者内部捕获(或通过 AsyncUncaughtExceptionHandler 全局处理);
- 性能考量:异步事件需合理配置线程池(如通过 @Async("customThreadPool") 指定自定义线程池),避免线程耗尽;
- 事务绑定:若发布者在事务中发布事件,默认情况下事件发布在事务 提交前 执行;若需事务提交后触发,可使用 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 。 四、典型应用场景 1. 业务解耦:核心业务(如订单创建)与辅助业务(如库存扣减、日志记录、消息通知)分离; 2. 状态变更通知:如订单状态从“待支付”变为“已支付”,触发物流创建、积分发放等; 3. 异步处理:耗时操作(如文件导出、邮件发送)通过异步订阅者执行,不阻塞主流程; 4. 跨模块通信:无需依赖模块间接口调用,通过事件传递消息,降低模块耦合。