📖 简介
@EventListener 是 Spring Framework 提供的一个强大注解,用于实现观察者模式(Observer Pattern),也称为事件驱动编程。它允许我们在 Spring 应用中发布和监听自定义事件,实现组件之间的解耦。
核心优势
• ✅ 松耦合:事件发布者和监听者之间无需直接依赖 • ✅ 异步处理:配合 @Async注解实现异步事件处理• ✅ 条件过滤:支持 SpEL 表达式进行条件监听 • ✅ 易于扩展:新增监听器无需修改现有代码
🎯 核心概念
• 事件(Event):继承自 ApplicationEvent的类,封装事件数据• 事件发布者(Publisher):通过 ApplicationEventPublisher发布事件• 事件监听器(Listener):使用 @EventListener注解的方法监听和处理事件
💡 实战示例:订单系统
下面我们通过一个订单系统的完整示例,演示 @EventListener 的各种用法。
1. 创建订单事件
/** * 订单创建事件 */@DatapublicclassOrderCreatedEventextendsApplicationEvent {privatefinal Long orderId; // 订单IDprivatefinal String productName; // 商品名称privatefinal Integer amount; // 订单金额publicOrderCreatedEvent(Object source, Long orderId, String productName, Integer amount) {super(source);this.orderId = orderId;this.productName = productName;this.amount = amount; }}2. 发布事件
@ServicepublicclassOrderService {@Autowiredprivate ApplicationEventPublisher eventPublisher;publicvoidcreateOrder(Long orderId, Integer userId, String productName, int amount) { System.out.println("create order : " + orderId);// 发布订单创建事件OrderCreatedEventevent=newOrderCreatedEvent(this, orderId, productName, amount); eventPublisher.publishEvent(event); }}3. 监听事件
@ComponentpublicclassOrderEventListener {// 同步监听 - 发送短信通知@EventListenerpublicvoidhandleOrderEvent(OrderCreatedEvent event) { System.out.println("【同步】发送短信通知,订单号: " + event.getOrderId()); }// 异步监听 - 更新库存@Async@EventListenerpublicvoidupdateInventory(OrderCreatedEvent event) { System.out.println("【异步】更新库存 - 商品: " + event.getProductName());try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("【异步】库存更新完成"); }// 条件监听 - 金额大于1000的订单@EventListener(condition = "#event.amount > 1000")publicvoidhandleHighValueOrder(OrderCreatedEvent event) { System.out.println("【高价值订单】订单金额: " + event.getAmount() + ",需要人工审核"); }}4. 测试接口
@RestController@RequestMapping("/api/event")publicclassEventController {@Autowiredprivate OrderService orderService;/** * 创建订单 */@GetMapping("/create-order")public String createOrder(@RequestParam Long orderId,@RequestParam String productName,@RequestParam Integer userId,@RequestParam Integer amount) { System.out.println("========== 开始创建订单 =========="); orderService.createOrder(orderId, userId, productName, amount); System.out.println("========== 订单创建请求完成 ==========");return"订单创建成功,订单ID: " + orderId; }}🚀 测试验证
测试命令
# 测试普通订单(金额 < 1000)curl "http://localhost:9005/api/event/create-order?orderId=1001&productName=手机&userId=1&amount=500"# 测试高价值订单(金额 > 1000)curl "http://localhost:9005/api/event/create-order?orderId=1002&productName=笔记本电脑&userId=1&amount=5000"执行结果分析
高价值订单(金额 5000)
========== 开始创建订单 ==========create order : 1002【同步】发送短信通知,订单号: 1002【高价值订单】订单金额: 5000,需要人工审核【异步】更新库存 - 商品: 笔记本电脑========== 订单创建请求完成 ==========【异步】库存更新完成🔧 高级特性
1. 同步监听
默认情况下,@EventListener 是同步执行的,监听器会在发布事件的线程中执行。
@EventListenerpublicvoidhandleOrderEvent(OrderCreatedEvent event) {// 同步执行 System.out.println("【同步】发送短信通知,订单号: " + event.getOrderId());}适用场景: 需要事务一致性、事件处理逻辑简单
2. 异步监听
配合 @Async 注解,可以在独立线程中异步处理事件。
@Async@EventListenerpublicvoidupdateInventory(OrderCreatedEvent event) {// 异步执行 System.out.println("【异步】更新库存 - 商品: " + event.getProductName());}适用场景: 耗时操作(如发送邮件、调用第三方接口)、需要提高系统吞吐量
注意: 使用 @Async 需要先在配置类上添加 @EnableAsync 注解。
3. 条件监听
使用 SpEL 表达式进行条件过滤,只有满足条件的事件才会被处理。
@EventListener(condition = "#event.amount > 1000")publicvoidhandleHighValueOrder(OrderCreatedEvent event) { System.out.println("【高价值订单】订单金额: " + event.getAmount() + ",需要人工审核");}常用 SpEL 表达式:
⚠️ 注意事项
1. 企业级应用的局限性
@EventListener 是 Spring 容器内的内存机制,存在以下限制:
| 应用重启 | |
| 应用崩溃 | |
| 分布式环境 |
2. 异步监听器的异常处理
异步监听器中的异常不会影响主流程,但需要单独处理。
@Async@EventListenerpublicvoidhandleAsyncEvent(OrderCreatedEvent event) {try {// 业务逻辑 } catch (Exception e) { System.err.println("异步事件处理失败: " + e.getMessage()); }}3. 事务传播
同步监听器默认参与当前事务,异步监听器在新事务中执行。
🎓 最佳实践
1. 企业级组合使用策略
在实际项目中,通常组合使用多种技术形成完整的事件驱动架构:
【同步关键业务】→ 【EventListener处理非关键逻辑】→ 【消息队列处理跨服务/持久化任务】典型场景示例:
@EventListener | ||
@EventListener@Async | ||
适用场景判断:
• ✅ 适合使用 @EventListener:应用内解耦、非关键业务逻辑、单机应用 • ❌ 不适合使用 @EventListener:需要持久化的任务、分布式系统、高可靠性要求
3. RuoYi-AI 实际案例:登录日志异步记录
事件定义:
// LogininforEvent.javapublicclassLogininforEventextendsApplicationEvent {privatefinal HttpServletRequest request;// ... }监听器实现:
@Async@EventListenerpublicvoidrecordLogininfor(LogininforEvent logininforEvent) {HttpServletRequestrequest= logininforEvent.getRequest();finalUserAgentuserAgent= UserAgentUtil.parse(request.getHeader("User-Agent"));finalStringip= ServletUtils.getClientIP(request);Stringaddress= AddressUtils.getRealAddressByIP(ip);Stringos= userAgent.getOs().getName();Stringbrowser= userAgent.getBrowser().getName();// 插入登录日志到数据库 insertLogininfor(logininfor);}作用: 当用户登录时,系统发布 LogininforEvent 事件,该方法异步监听并记录登录日志(包括 IP 地址、浏览器、操作系统等),避免阻塞主线程,提升登录响应速度。
4. 命名规范
• 事件类以 Event结尾:OrderCreatedEvent• 监听器类以 Listener结尾:OrderEventListener• 监听方法以 handle开头:handleOrderEvent
❓ 常见问题
Q1: 同步监听和异步监听有什么区别?
A:
• 同步监听:在发布事件的线程中执行,会阻塞主流程,适合需要事务一致性的场景 • 异步监听:在独立线程中执行,不阻塞主流程,适合耗时操作
Q2: 如何确保异步监听器的执行顺序?
A: 异步监听器的执行顺序由线程池调度决定,无法保证顺序。如果需要顺序执行,应该使用同步监听器。
Q3: 如何避免事件处理失败影响主流程?
A:
• 使用异步监听器 • 在同步监听器中添加异常处理
@EventListenerpublicvoidhandleEvent(OrderCreatedEvent event) {try {// 业务逻辑 } catch (Exception e) { System.err.println("事件处理失败: " + e.getMessage()); }}📝 总结
@EventListener 注解是 Spring 框架中实现事件驱动编程的核心工具,通过它可以轻松实现组件之间的解耦和异步处理。
核心要点
1. 事件驱动:通过发布-订阅模式实现组件解耦 2. 同步异步:支持同步和异步两种事件处理方式 3. 条件过滤:使用 SpEL 表达式实现灵活的事件过滤 4. 易于扩展:新增监听器无需修改现有代码
适用场景
• 订单系统(订单创建、支付、发货等事件) • 用户系统(注册、登录、注销等事件) • 消息通知(邮件、短信、推送等) • 数据同步(缓存更新、索引同步等)
通过合理使用 @EventListener,可以构建出更加灵活、可维护的 Spring 应用程序。
🔗 项目源码
• jing-yes-java/jingyes-spring-async
夜雨聆风