Spring 订阅发布模式(事件驱动模型)

30 阅读5分钟

Spring 订阅发布模式(事件驱动模型)   Spring 订阅发布模式基于 观察者设计模式,通过  ApplicationEvent (事件)、 ApplicationListener (监听器/订阅者)、 ApplicationEventPublisher (发布者)三者协同实现,核心用于解耦组件间通信,适用于业务逻辑分离(如操作日志记录、异步通知、状态变更回调等场景)。   一、核心组件与原理  

  1. 核心角色  
  • 事件(Event):继承  ApplicationEvent  的实体类,封装需要传递的消息数据(如用户注册事件包含用户ID、注册时间等)。
  • 发布者(Publisher):通过  ApplicationEventPublisher  接口发布事件,Spring 容器中所有 Bean 可直接注入该接口(或通过  ApplicationContext  间接调用,因  ApplicationContext  继承了  ApplicationEventPublisher )。
  • 订阅者(Listener):实现  ApplicationListener  接口或通过  @EventListener  注解声明,用于监听指定事件并执行回调逻辑。  
  1. 核心原理   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() + "]发送欢迎短信(异步执行,不阻塞主流程)");     } }     三、关键特性与注意事项  

  1. 事件传播机制  
  • 默认同步:发布者调用  publishEvent()  后,会阻塞直到所有订阅者执行完成;
  • 异步支持:通过  @Async  实现,需配合  @EnableAsync ,订阅者在独立线程执行;
  • 事件继承:若事件 B 继承事件 A,监听事件 A 的订阅者会同时接收事件 A 和 B(多态特性)。  
  1. 顺序执行控制   若多个订阅者监听同一事件,可通过  @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. 跨模块通信:无需依赖模块间接口调用,通过事件传递消息,降低模块耦合。