这是一个或许对你有用的社群 一对一交流/面试小册/简历优化/求职解惑欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料《项目实战视频》从书中学往事上“练”《互联网高频面试题》面朝简历学习春暖花开《架构 x 系统设计》摧枯拉朽掌控面试高频场景题《精进 Java 学习指南》系统学习互联网主流技术栈《必读 Java 源码专栏》知其然知其所以然这是一个或许对你有用的开源项目国产Star破10w的开源项目前端包括管理后台、微信小程序后端支持单体、微服务架构RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRM、AI大模型、IoT物联网等功能多模块https://gitee.com/zhijiantianya/ruoyi-vue-pro微服务https://gitee.com/zhijiantianya/yudao-cloud视频教程https://doc.iocoder.cn【国内首批】支持 JDK17/21SpringBoot3、JDK8/11Spring Boot2双版本问题越写越胖的 Service 方法四个核心角色两种使用方式接口实现 vs 注解Spring 内置事件同步还是异步TransactionalEventListener事务提交后再执行源码解析事件是怎么走到监听器的踩坑实录Spring Event vs MQ怎么选生产级最佳实践回到开头那个问题问题越写越胖的 Service 方法工作年头长了见过太多这样的 Service 方法public void createOrder(Order order) { // 1. 保存订单 orderDao.save(order); // 2. 发送短信通知 smsService.sendOrderConfirm(order.getUserPhone(), order.getId()); // 3. 更新用户积分 pointService.addPoints(order.getUserId(), order.getTotalAmount()); // 4. 记录操作日志 auditService.log(CREATE_ORDER, order.getId()); // 5. 推送站内消息 notifyService.push(order.getUserId(), 您的订单已提交); // 6. 触发风控检查 riskService.check(order); }技术上没 Bug跑得也好好的。但有一天产品说下单后还要同步购物车你得改运营说VIP 下单要额外赠券你还得改。核心下单逻辑就这样被各种非核心需求越填越厚所有依赖硬编码在一起。哪天riskService挂了整个下单都跟着挂——这明显不合理。很多人的第一反应是上 MQ。但如果是单体应用或者只是同 JVM 内的逻辑解耦部署一套 MQ 中间件有点杀鸡用牛刀。Spring 早就提供了一套轻量级事件驱动机制——ApplicationEventEventListener本质是发布-订阅模式零额外依赖Spring Boot 开箱即用。基于 Spring Boot MyBatis Plus Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/ruoyi-vue-pro视频教程https://doc.iocoder.cn/video/四个核心角色Spring事件机制核心组件全景事件、发布者、广播器、监听者四大角色的关系图1. ApplicationEvent事件数据载体。继承自 JDKEventObject包含source和timestamp。Spring 4.2 后支持发布任意对象不继承也行内部自动封装为PayloadApplicationEvent。2. ApplicationEventPublisher发布者只有一个方法publishEvent()。ApplicationContext实现了这个接口注入ApplicationEventPublisher更优雅——依赖接口而非实现。3. ApplicationEventMulticaster广播器整个机制的核心调度引擎。维护所有已注册的监听器列表负责将事件分发给匹配的监听者。默认实现SimpleApplicationEventMulticaster。4. ApplicationListener监听者接收并处理事件通过实现接口或注解定义。角色对应组件责任事件ApplicationEvent信息载体发布者ApplicationEventPublisher触发事件广播器ApplicationEventMulticaster调度分发监听者ApplicationListener/EventListener处理事件基于 Spring Cloud Alibaba Gateway Nacos RocketMQ Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能项目地址https://github.com/YunaiV/yudao-cloud视频教程https://doc.iocoder.cn/video/两种使用方式接口实现 vs 注解方式一实现 ApplicationListener 接口传统方式Step 1定义事件对象public class OrderCreatedEvent extends ApplicationEvent { privatefinal String orderId; privatefinal Long userId; privatefinal BigDecimal totalAmount; public OrderCreatedEvent(Object source, String orderId, Long userId, BigDecimal totalAmount) { super(source); this.orderId orderId; this.userId userId; this.totalAmount totalAmount; } public String getOrderId() { return orderId; } public Long getUserId() { return userId; } public BigDecimal getTotalAmount() { return totalAmount; } }Step 2发布事件Service publicclass OrderService { privatefinal ApplicationEventPublisher eventPublisher; public OrderService(ApplicationEventPublisher eventPublisher) { this.eventPublisher eventPublisher; } Transactional public Order createOrder(CreateOrderRequest req) { Order order buildOrder(req); orderDao.save(order); eventPublisher.publishEvent( new OrderCreatedEvent(this, order.getId(), order.getUserId(), order.getTotalAmount()) ); return order; } }Step 3实现监听器Component publicclass SmsNotifyListener implements ApplicationListenerOrderCreatedEvent { Override public void onApplicationEvent(OrderCreatedEvent event) { smsService.send(event.getUserId(), 订单 event.getOrderId() 已提交); } } Component publicclass PointsListener implements ApplicationListenerOrderCreatedEvent { Override public void onApplicationEvent(OrderCreatedEvent event) { pointService.addPoints(event.getUserId(), event.getTotalAmount()); } }缺点一个监听器只能监听一种事件类型类里只能有一个处理方法。方式二EventListener 注解推荐Spring 4.2 引入灵活度大幅提升主流写法Component publicclass OrderEventHandler { EventListener public void handleSms(OrderCreatedEvent event) { smsService.send(event.getUserId(), 订单已提交); } // 同时监听多种事件 EventListener(value {OrderCreatedEvent.class, OrderPaidEvent.class}) public void handleOrderStateChange(ApplicationEvent event) { auditService.log(event); } // 条件监听只处理金额 1000 元的 EventListener(condition #event.totalAmount 1000) public void handleHighValueOrder(OrderCreatedEvent event) { vipService.notifyHighValue(event.getOrderId()); } // 返回新事件自动广播下一个 EventListener public InventoryUpdateEvent handleOrder(OrderCreatedEvent event) { returnnew InventoryUpdateEvent(this, event.getOrderId()); } }几个少被提及的功能**condition** 支持 SpEL 过滤返回值如果是事件对象会自动发布适合事件链Order控制执行顺序。EventListener Order(1) // 先执行 Async public void riskCheck(OrderCreatedEvent event) { ... } EventListener Order(2) // 后执行 Async public void sendSms(OrderCreatedEvent event) { ... }Spring 内置事件Spring 在容器生命周期关键节点也会发布事件Spring内置事件类型列表事件触发时机常用场景ContextRefreshedEvent容器初始化或刷新完成预热缓存、加载配置ContextStartedEvent调用context.start()启动定时任务ContextStoppedEvent调用context.stop()清理资源ContextClosedEvent容器关闭优雅关机RequestHandledEventHTTP 请求处理完毕请求监控最常用的是ContextRefreshedEvent预热缓存的经典用法Component public class CachePreloadListener { EventListener(ContextRefreshedEvent.class) public void preloadCache(ContextRefreshedEvent event) { // 父子容器场景会触发两次需要防守 if (event.getApplicationContext().getParent() ! null) { return; } CompletableFuture.runAsync(() - { provinceService.loadAll(); hotProductService.preload(); log.info(缓存预热完成); }); } }Spring Boot 里有更好用的ApplicationReadyEvent只触发一次EventListener(ApplicationReadyEvent.class) public void onReady() { cacheService.initAll(); }同步还是异步Spring 事件默认同步——publishEvent()会阻塞等所有监听器执行完才继续。好处是简单、对事务友好坏处是监听器卡顿会拖慢发布者。开启异步三步搞定第一步开启异步支持SpringBootApplication EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }第二步配置专用线程池强烈建议否则会用ForkJoinPool.commonPoolConfiguration public class AsyncConfig { Bean(eventExecutor) public Executor eventTaskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(16); executor.setQueueCapacity(500); executor.setThreadNamePrefix(event-async-); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }第三步给监听器加 AsyncAsync(eventExecutor) EventListener public void handleSms(OrderCreatedEvent event) { smsService.send(event.getUserId(), 订单已提交); }两个必知的异步局限1. 异常不传播异步监听器的异常在线程池线程中自行消化发布者完全感知不到。必须在监听器内 try-catch。2. 返回值发布失效加了Async后返回事件对象不会自动发布需手动publishEvent。Async(eventExecutor) EventListener public void handleSms(OrderCreatedEvent event) { try { smsService.send(event.getUserId(), 订单已提交); } catch (Exception e) { log.error(短信发送失败订单 ID: {}, event.getOrderId(), e); alarmService.notify(SMS_FAILED, e.getMessage()); } }TransactionalEventListener事务提交后再执行场景用户修改订单后删缓存。如果用普通EventListener缓存删了但事务可能回滚——缓存击穿。TransactionalEventListener解决这个问题TransactionalEventListener(phase TransactionPhase.AFTER_COMMIT) public void cleanCache(OrderUpdatedEvent event) { redisTemplate.delete(order: event.getOrderId()); }phase触发时机AFTER_COMMIT默认事务成功提交后AFTER_ROLLBACK事务回滚后AFTER_COMPLETION事务完成后无论成败BEFORE_COMMIT事务提交前两个隐形陷阱1. 没有事务时事件会被丢弃// 没有 Transactional 的方法发布事件 public void someMethod() { eventPublisher.publishEvent(new OrderUpdatedEvent(...)); // TransactionalEventListener 会直接丢弃 }加fallbackExecution true解决TransactionalEventListener(phase TransactionPhase.AFTER_COMMIT, fallbackExecution true) public void cleanCache(OrderUpdatedEvent event) { redisTemplate.delete(order: event.getOrderId()); }2. AFTER_COMMIT 阶段里起不了新事务事务已提交加了Transactional默认也不会新开事务必须指定Propagation.REQUIRES_NEW。源码解析事件是怎么走到监听器的注册阶段容器refresh()过程中完成两件事初始化广播器protected void initApplicationEventMulticaster() { ConfigurableListableBeanFactory beanFactory getBeanFactory(); if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { this.applicationEventMulticaster beanFactory.getBean(...); } else { this.applicationEventMulticaster new SimpleApplicationEventMulticaster(beanFactory); } }注册监听器先注册编程式添加的监听器再从 BeanFactory 找所有ApplicationListener接口的 Bean。EventListener注解由EventListenerMethodProcessor处理——在所有单例 Bean 初始化完成后扫描带注解的方法封装为ApplicationListenerMethodAdapter注册到广播器。分发阶段// SimpleApplicationEventMulticaster#multicastEvent() public void multicastEvent(ApplicationEvent event, ResolvableType eventType) { ResolvableType type (eventType ! null ? eventType : resolveDefaultEventType(event)); Executor executor getTaskExecutor(); for (ApplicationListener? listener : getApplicationListeners(event, type)) { if (executor ! null) { executor.execute(() - invokeListener(listener, event)); } else { invokeListener(listener, event); } } }getApplicationListeners()内部有缓存首次调用遍历所有监听器做类型匹配之后走缓存。完整调用链publishEvent() └─ multicastEvent() └─ getApplicationListeners()带缓存的匹配 └─ invokeListener() └─ doInvokeListener() └─ listener.onApplicationEvent() ← 你的业务代码踩坑实录坑 1事件对象设计成可变的EventListener public void handleA(OrderCreatedEvent event) { event.setStatus(PROCESSING); // 修改了事件对象 } EventListener public void handleB(OrderCreatedEvent event) { // handleA 已经改了 status这里看到脏数据 doSomething(event.getStatus()); }多个监听器共享同一个事件对象。事件对象必须不可变属性加final不提供 setter。坑 2开了 Async 却没开 EnableAsyncEventListener Async public void asyncHandle(OrderCreatedEvent event) { // 看起来异步实际同步执行而且无任何报错 }Async不生效的表现是性能没提升却找不到原因。**EnableAsync必须加在主类或配置类上。**坑 3事件循环调用EventListener public void handleA(EventA event) { eventPublisher.publishEvent(new EventB()); } EventListener public void handleB(EventB event) { eventPublisher.publishEvent(new EventA()); // 死循环StackOverflowError }设计事件时要明确依赖关系避免形成环。坑 4TransactionalEventListener 无事务时默默丢事件上面已经提到。没事务 没设fallbackExecution true 事件被默默丢弃调试时极难发现。Spring Event vs MQ怎么选Spring Event 与 MQ 对比选型决策树对比维度Spring EventMQKafka/RocketMQ适用场景同 JVM 内模块解耦跨服务通信可靠性进程崩溃就丢事件持久化 重试延迟内存级≈ 0受网络影响开发成本零依赖注解即用需部署中间件一致性本地事务保障需分布式事务吞吐量异步批量 ~9w/s受网络并发限制简单说单体应用 / 同 JVM 逻辑解耦 / 不要求消息持久化 → Spring Event微服务跨服务通信 / 高可靠消息传输 → MQ。生产级最佳实践1. 事件对象保持轻量只传 ID不传大对象。public class OrderCreatedEvent extends ApplicationEvent { private final String orderId; private final String version 1.0; }2. 一个监听器只做一件事PaymentListener只处理支付CouponListener只发券。3. 异步监听器必须自己捕异常。4. 加监控关键链路的监听器加耗时统计和告警。5. 事件命名要语义化用过去式动词——OrderCreatedEvent、PaymentSuccessEvent。不要用DoSomethingEvent。回到开头那个问题那个越写越胖的createOrder用事件机制改造Transactional public Order createOrder(CreateOrderRequest req) { Order order buildOrder(req); orderDao.save(order); eventPublisher.publishEvent(new OrderCreatedEvent(this, order)); return order; } // 新增功能加个监听器就好不动核心代码 Component publicclass NewFeatureListener { EventListener public void handleNewFeature(OrderCreatedEvent event) { // 新功能在这里 } }一个经得起考验的系统不是靠把所有逻辑塞进一个方法里而是让不同模块各司其职。Spring 事件机制正是实现这种协作的工具。欢迎加入我的知识星球全面提升技术能力。 加入方式“长按”或“扫描”下方二维码噢星球的内容包括项目实战、面试招聘、源码解析、学习路线。文章有帮助的话在看转发吧。 谢谢支持哟 (*^__^*