Spring 订阅发布模式(事件驱动模型)
 
Spring 订阅发布模式基于 观察者设计模式,通过  ApplicationEvent (事件)、 ApplicationListener (监听器/订阅者)、 ApplicationEventPublisher (发布者)三者协同实现,核心用于解耦组件间通信,适用于业务逻辑分离(如操作日志记录、异步通知、状态变更回调等场景)。
 
一、核心组件与原理
 
1. 核心角色
 
- 事件(Event):继承  ApplicationEvent  的实体类,封装需要传递的消息数据(如用户注册事件包含用户ID、注册时间等)。
- 发布者(Publisher):通过  ApplicationEventPublisher  接口发布事件,Spring 容器中所有 Bean 可直接注入该接口(或通过  ApplicationContext  间接调用,因  ApplicationContext  继承了  ApplicationEventPublisher )。
- 订阅者(Listener):实现  ApplicationListener  接口或通过  @EventListener  注解声明,用于监听指定事件并执行回调逻辑。
 
2. 核心原理
 
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<UserRegisterEvent> {

    // 重写 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(多态特性)。
 
2. 顺序执行控制
 
若多个订阅者监听同一事件,可通过  @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. 跨模块通信:无需依赖模块间接口调用,通过事件传递消息,降低模块耦合。
 
 

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐