Spring 从核心模块开始就提供了轻量级的事件驱动机制,它构建在 IoC 容器之上,能够让不同 Bean 之间通过发布—订阅模式进行通信,而不需要显式的直接调用。这种机制天然适合实现业务解耦:例如用户注册后,需要发送欢迎邮件、初始化账户积分、记录审计日志等,这些后续操作完全可以通过事件来触发,注册服务本身无需知道有哪些后续处理。
Spring 事件体系的核心接口和类均位于 org.springframework.context 包下,理解其中几个关键角色即可快速上手。
11.1.1 自定义事件:携带业务数据
事件本身是一个对象,用来承载需要传递的业务信息。在 Spring 中,自定义事件通常直接继承 ApplicationEvent 抽象类:
import org.springframework.context.ApplicationEvent;
public class UserRegisteredEvent extends ApplicationEvent {
private final Long userId;
private final String email;
// source 通常传入事件发生的源对象,可以传 this 或具体的组件
public UserRegisteredEvent(Object source, Long userId, String email) {
super(source);
this.userId = userId;
this.email = email;
}
// getters...
}
从 Spring 4.2 开始,你也可以不继承 ApplicationEvent,直接使用任意对象作为事件。但为了可读性和明确事件语义,建议还是继承 ApplicationEvent 或者使用 PayloadApplicationEvent 进行包装。
11.1.2 事件发布:注入事件发布器
任何被 Spring 管理的 Bean 都可以通过 ApplicationEventPublisher 发布事件。最为简便的方式是直接注入该接口:
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final ApplicationEventPublisher publisher;
public UserService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void register(String email) {
// 执行注册逻辑,保存用户...
Long userId = 123L; // 假设已生成ID
// 发布事件
publisher.publishEvent(new UserRegisteredEvent(this, userId, email));
}
}
如果你的服务实现了 ApplicationEventPublisherAware 接口或直接访问 ApplicationContext,也可以获得发布能力,但构造器注入是最清晰的方式。publishEvent() 被调用后,Spring 会同步调用所有匹配的监听器——默认所有处理都在同一线程内串行执行。
11.1.3 事件监听:注解驱动与接口驱动
Spring 提供了两种监听方式,推荐使用更为灵活的 @EventListener 注解。
1. 使用 @EventListener 注解
只需在任意 Spring Bean 的公开方法上标注 @EventListener,方法参数类型即为要监听的事件类型:
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class RegistrationListener {
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
System.out.println("用户注册成功,ID:" + event.getUserId());
// 发送欢迎邮件、初始化积分等
}
}
该方法会在 UserRegisteredEvent 发布时被自动调用。你可以在同一个类中定义多个监听方法,监听不同类型的事件,完全松耦合。
高级用法:
- 条件过滤:通过
condition属性使用 SpEL 表达式过滤事件,例如只处理特定来源的事件:
@EventListener(condition = "#event.email.endsWith('@vip.com')")
public void handleVipUser(UserRegisteredEvent event) { ... }
- 监听多个事件类型:一个方法可以监听多种事件,只需在一个注解中指定多个类:
@EventListener({UserRegisteredEvent.class, PasswordResetEvent.class})
public void handleEvents(Object event) { ... }
- 返回新事件:监听方法可以返回一个新对象,Spring 会将其作为新事件再次发布(用于事件链):
@EventListener
public SendEmailEvent handleAndPublish(UserRegisteredEvent event) {
return new SendEmailEvent(event.getEmail());
}
2. 实现 ApplicationListener 接口
这是传统的监听方式,需要声明泛型:
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class LegacyRegistrationListener
implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
// 处理逻辑
}
}
这种方式代码稍显冗长,且不支持条件过滤等增强特性,在非必要情况下更推荐注解方式。
11.1.4 真实场景:注册后发布邮件与短信
下面展示一个更完整的示例:用户注册后,通过事件驱动异步发送邮件和短信,避免注册主流程阻塞。
第一步,开启异步支持(在配置类上加 @EnableAsync):
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第二步,定义监听器并标记为异步:
@Component
public class NotificationListener {
@Async
@EventListener
public void sendWelcomeEmail(UserRegisteredEvent event) {
// 模拟发送邮件(耗时操作)
System.out.println("正在发送邮件到 " + event.getEmail());
// ...
}
@Async
@EventListener
public void sendWelcomeSms(UserRegisteredEvent event) {
// 模拟发送短信
System.out.println("正在发送短信通知 " + event.getUserId());
// ...
}
}
这样,注册服务调用 publisher.publishEvent(event) 后,邮件和短信的发送会在独立的线程池中异步并行执行,不会延长用户请求的响应时间。如果需要保证事件在事务提交后再发布(避免事务回滚后事件已触发),可以结合 @TransactionalEventListener 注解,指定事务阶段(如 AFTER_COMMIT)。
11.1.5 注意事项
- 同步 vs 异步:默认同步,若某个监听器执行耗时或抛出异常,会影响整个事件发布线程。根据业务需要选择异步,并配置合理的线程池。
- 异常处理:异步监听器中发生的异常不会抛回发布者,需要监听器内部自行捕获处理,或使用
AsyncUncaughtExceptionHandler统一记录。 - 事件顺序:若多个监听器需要按顺序执行,可以在方法上添加
@Order注解,但异步情况下顺序无法保证。 - 事务边界:如需事件与事务绑定(如只有事务提交后才执行),使用
@TransactionalEventListener并设置阶段。 - 避免循环依赖:小心监听器内部发布新事件导致的无限循环。
Spring 的事件驱动模型极其简单却非常强大,它天然融入到 IoC 容器中,无需引入额外的消息中间件即可在单应用内实现高度的模块解耦。当你发现某个业务操作需要“附带”多个副操作时,将其转化为事件发布与监听,往往能让代码更清晰、更易扩展。