Spring 订阅发布模式(事件驱动模型)
Spring 订阅发布模式基于 观察者设计模式,通过ApplicationEvent (事件)、 ApplicationListener (监听器/订阅者)、 ApplicationEventPublisher (发布者)三者协同实现,核心用于解耦组件间通信,适用于业务逻辑分离(如操作日志记录、异步通知、状态变更回调等场景)。System.out.println("异步监听:向用户[" + eve
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. 跨模块通信:无需依赖模块间接口调用,通过事件传递消息,降低模块耦合。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)