你是否还在业务代码里写硬编码的系统初始化逻辑?上周我重构了一个500行的初始化方法,用ApplicationListener把它拆成了5个独立的监听器,系统可维护性瞬间提升几个档次...
01、一个典型的系统初始化烂摊子
上周团队让我帮忙review一个用户中心的代码,我看到这样一个服务类:
@Servicepublic class UserSystemInitializer { @Autowired private CacheService cacheService; @Autowired private MessageQueueService mqService; @Autowired private ScheduledExecutorService scheduler; @Autowired private ExternalServiceClient externalClient; @PostConstruct public void initEverything() { log.info("开始初始化用户中心系统..."); // 1. 加载缓存 try { cacheService.loadAllUserCache(); } catch (Exception e) { log.error("缓存加载失败", e); } // 2. 启动消息监听 try { mqService.startListenUserEvents(); } catch (Exception e) { log.error("消息监听启动失败", e); } // 3. 启动定时任务 try { scheduler.scheduleAtFixedRate(this::syncExternalData, 0, 5, TimeUnit.MINUTES); } catch (Exception e) { log.error("定时任务启动失败", e); } // 4. 建立外部服务连接 try { externalClient.preheatConnection(); } catch (Exception e) { log.error("外部服务连接预热失败", e); } // 5. 发送系统启动通知 try { notifySystemStarted(); } catch (Exception e) { log.error("系统启动通知发送失败", e); } log.info("用户中心系统初始化完成"); } // 后面还有十几个私有方法...}
看到这段代码,我仿佛看到了三年前的自己。这种"一锅炖"的初始化方式存在几个致命问题:
- 1. 职责不清:一个方法做多件事,违反了单一职责原则
- 2. 异常处理混乱:某个步骤失败不影响其他步骤?但日志里根本看不出哪里出了问题
- 3. 难以扩展:想要新增一个初始化步骤,得在这个500行的方法里找地方插入
- 4. 无法复用:其他模块想要类似的初始化逻辑,只能复制粘贴
02、Spring事件监听机制:观察者模式的完美实践
Spring的事件机制是基于观察者模式的实现,核心组件非常简单:
事件发布者 (Publisher) → 事件对象 (Event) → 事件监听器 (Listener)
Spring应用启动过程中会发布一系列标准事件,形成了一个完整的生命周期:
ApplicationStartingEvent ↓ApplicationEnvironmentPreparedEvent ↓ApplicationContextInitializedEvent ↓ApplicationPreparedEvent ↓ContextRefreshedEvent ← 最常用的! ↓ApplicationStartedEvent ↓ApplicationReadyEvent ← 应用完全就绪 ↓ContextClosedEvent ← 应用关闭
我们可以监听这些事件,在不同阶段执行相应的初始化逻辑。这就像给应用装上了"触发器"!
03、实战:将烂摊子重构为优雅的监听器
让我们用ApplicationListener来重构上面的烂代码。首先,我们创建一个基础监听器:
@Component@Slf4jpublic abstract class BaseSystemListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent event) { if (shouldHandle(event)) { log.info("[{}] 开始处理事件: {}", getListenerName(), event.getClass().getSimpleName()); long startTime = System.currentTimeMillis(); try { doHandle(event); long duration = System.currentTimeMillis() - startTime; log.info("[{}] 处理完成,耗时 {}ms", getListenerName(), duration); // 记录监控指标 Metrics.timer("listener." + getListenerName() + ".duration", duration); Metrics.counter("listener." + getListenerName() + ".success").increment(); } catch (Exception e) { log.error("[{}] 处理失败", getListenerName(), e); Metrics.counter("listener." + getListenerName() + ".failure").increment(); handleFailure(e); } } } protected abstract boolean shouldHandle(ApplicationEvent event); protected abstract void doHandle(ApplicationEvent event); protected void handleFailure(Exception e) { // 默认实现:只记录日志,不中断其他监听器 } protected abstract String getListenerName();}
接下来,我们把原来的一锅炖拆分成独立的监听器:
1. 缓存初始化监听器
@Component@Slf4j@Order(1) // 缓存应该最先初始化public class CacheInitListener extends BaseSystemListener { @Autowired private UserCacheService userCacheService; @Autowired private ConfigCacheService configCacheService; @Override protected boolean shouldHandle(ApplicationEvent event) { // 当应用上下文刷新完成后初始化缓存 return event instanceof ContextRefreshedEvent; } @Override protected void doHandle(ApplicationEvent event) { // 并行加载不同类型的缓存 CompletableFuture<Void> userCacheFuture = CompletableFuture.runAsync(() -> { userCacheService.loadAllUsers(); }); CompletableFuture<Void> configCacheFuture = CompletableFuture.runAsync(() -> { configCacheService.loadSystemConfig(); }); // 等待所有缓存加载完成 CompletableFuture.allOf(userCacheFuture, configCacheFuture).join(); } @Override protected void handleFailure(Exception e) { // 缓存加载失败是严重问题,需要抛出异常 throw new RuntimeException("缓存初始化失败,系统无法启动", e); } @Override protected String getListenerName() { return "cacheInit"; }}
2. 消息队列监听器
@Component@Slf4j@Order(2) // 缓存初始化后才能监听消息public class MessageQueueListener extends BaseSystemListener { @Autowired private UserEventListener userEventListener; @Autowired private OrderEventListener orderEventListener; @Override protected boolean shouldHandle(ApplicationEvent event) { // 在应用完全就绪后启动消息监听 return event instanceof ApplicationReadyEvent; } @Override protected void doHandle(ApplicationEvent event) { // 启动用户事件监听(不阻塞主线程) new Thread(() -> { userEventListener.startListening(); }, "user-event-listener").start(); // 启动订单事件监听 new Thread(() -> { orderEventListener.startListening(); }, "order-event-listener").start(); } @Override protected String getListenerName() { return "mqListener"; }}
3. 外部服务连接监听器
@Component@Slf4j@Order(3)@ConditionalOnProperty(name = "external.service.enabled", havingValue = "true")public class ExternalServiceListener extends BaseSystemListener { @Autowired private ExternalServiceHealthCheck healthCheck; @Override protected boolean shouldHandle(ApplicationEvent event) { // 在应用启动时检查外部服务状态 return event instanceof ApplicationStartedEvent; } @Override protected void doHandle(ApplicationEvent event) { // 检查外部服务健康状态 boolean isHealthy = healthCheck.checkAllServices(); if (!isHealthy) { log.warn("外部服务状态不健康,但系统继续启动"); // 可以触发降级策略 FallbackStrategy.enableDegradedMode(); } } @Override protected String getListenerName() { return "externalService"; }}
4. 定时任务监听器
@Component@Slf4jpublic class ScheduledTaskListener implements ApplicationListener<ApplicationReadyEvent>, SmartLifecycle { @Autowired private ExternalDataSyncTask syncTask; private ScheduledExecutorService scheduler; private volatile boolean running = false; @Override public void onApplicationEvent(ApplicationReadyEvent event) { // 应用就绪后启动定时任务 start(); } @Override public void start() { if (!running) { log.info("启动定时任务管理器"); scheduler = Executors.newScheduledThreadPool(2); // 每5分钟同步外部数据 scheduler.scheduleAtFixedRate(syncTask::syncUsers, 0, 5, TimeUnit.MINUTES); // 每小时清理临时数据 scheduler.scheduleAtFixedRate(syncTask::cleanTempData, 1, 1, TimeUnit.HOURS); running = true; } } @Override public void stop() { if (running && scheduler != null) { log.info("关闭定时任务管理器"); scheduler.shutdown(); try { if (!scheduler.awaitTermination(10, TimeUnit.SECONDS)) { scheduler.shutdownNow(); } } catch (InterruptedException e) { scheduler.shutdownNow(); Thread.currentThread().interrupt(); } running = false; } } @Override public boolean isRunning() { return running; }}
04、高级技巧:自定义事件解耦业务模块
除了监听Spring内置事件,我们还可以发布自定义事件,实现业务模块间的解耦。这是真正的"神器"用法!
场景:用户注册后的多系统联动
传统写法(紧耦合):
@Servicepublic class UserServiceImpl implements UserService { @Autowired private EmailService emailService; @Autowired private PointsService pointsService; @Autowired private RecommendationService recommendationService; @Override public User register(UserRegisterRequest request) { // 1. 创建用户 User user = createUser(request); // 2. 发送欢迎邮件 emailService.sendWelcomeEmail(user); // 3. 赠送积分 pointsService.grantRegistrationPoints(user); // 4. 生成推荐内容 recommendationService.generateForNewUser(user); // 5. 发送系统通知 notificationService.notifyAdmins(user); // 每新增一个需求,这里就要加一行代码... return user; }}
使用事件监听模式(松耦合):
// 1. 定义用户注册成功事件public class UserRegisteredEvent extends ApplicationEvent { private final User user; public UserRegisteredEvent(Object source, User user) { super(source); this.user = user; } public User getUser() { return user; }}// 2. 用户服务只负责发布事件@Servicepublic class UserServiceImpl implements UserService { @Autowired private ApplicationEventPublisher eventPublisher; @Override @Transactional public User register(UserRegisterRequest request) { User user = createUser(request); // 发布用户注册事件 eventPublisher.publishEvent(new UserRegisteredEvent(this, user)); return user; }}// 3. 邮件服务监听事件@Component@Slf4jpublic class EmailListener { @Async // 异步执行,不阻塞主流程 @EventListener @Order(1) // 最先执行 public void handleUserRegistered(UserRegisteredEvent event) { User user = event.getUser(); log.info("发送欢迎邮件给用户: {}", user.getEmail()); // 发送邮件的具体逻辑 }}// 4. 积分服务监听事件@Component@Slf4jpublic class PointsListener { @Async @EventListener @Order(2) public void handleUserRegistered(UserRegisteredEvent event) { User user = event.getUser(); log.info("为新用户赠送积分: {}", user.getId()); // 赠送积分的具体逻辑 }}// 5. 新增一个需求:用户注册时记录审计日志@Component@Slf4jpublic class AuditListener { @EventListener public void handleUserRegistered(UserRegisteredEvent event) { // 新增需求时,只需要新增一个Listener,无需修改原有代码 log.info("记录用户注册审计日志: {}", event.getUser().getId()); }}
事件传播的事务边界处理
这里有一个重要细节:默认情况下,事件监听器会在发布事件的方法事务提交后执行。但我们可以通过@TransactionalEventListener来精确控制:
@Componentpublic class OrderListener { // 默认:事务提交后执行 @TransactionalEventListener public void handleOrderCreated(OrderCreatedEvent event) { // 发送订单确认邮件 } // 事务提交前执行 @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void handleBeforeCommit(OrderCreatedEvent event) { // 检查库存等关键业务 } // 事务回滚后执行 @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void handleAfterRollback(OrderCreatedEvent event) { // 记录失败日志,发送警报 } // 事务完成后执行(无论提交还是回滚) @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION) public void handleAfterCompletion(OrderCreatedEvent event) { // 清理临时数据 }}
05、性能优化:异步事件与线程池配置
如果监听器逻辑比较耗时,我们可以使用异步执行来避免阻塞主流程:
@Configuration@EnableAsyncpublic class AsyncEventConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("event-listener-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> { log.error("异步事件监听执行失败, method: {}", method.getName(), ex); // 可以在这里发送警报 }; }}
然后给监听器加上@Async注解:
@Component@Slf4jpublic class DataSyncListener { @Async @EventListener public void handleDataChanged(DataChangedEvent event) { // 这里会在线程池中异步执行 syncToExternalSystem(event.getData()); }}
06、实际案例:电商系统的库存同步
让我分享一个真实的电商系统案例。我们需要在商品库存变化时,同步更新Redis缓存、更新ES搜索引擎、通知风控系统、发送库存警报。
传统做法是在库存服务中调用所有相关服务,结果代码变成了这样:
@Servicepublic class InventoryServiceImpl { public void updateInventory(Long skuId, Integer delta) { // 更新数据库 inventoryDao.update(skuId, delta); // 更新Redis缓存 redisTemplate.opsForValue().set("inventory:" + skuId, getCurrentStock(skuId)); // 更新ES esClient.updateInventory(skuId, getCurrentStock(skuId)); // 通知风控系统 riskControlService.notifyInventoryChange(skuId, delta); // 发送低库存警报 if (getCurrentStock(skuId) < 10) { alertService.sendLowStockAlert(skuId); } // 记录操作日志 auditService.logInventoryChange(skuId, delta); }}
使用事件监听模式重构后:
@Servicepublic class InventoryServiceImpl { @Autowired private ApplicationEventPublisher eventPublisher; @Transactional public void updateInventory(Long skuId, Integer delta) { // 只负责核心业务逻辑 Integer oldStock = inventoryDao.getStock(skuId); inventoryDao.update(skuId, delta); Integer newStock = oldStock + delta; // 发布库存变化事件 eventPublisher.publishEvent(new InventoryChangedEvent(this, skuId, oldStock, newStock, delta)); }}// 各种监听器各司其职@Component class CacheUpdateListener { /* 更新Redis */ }@Component class EsSyncListener { /* 更新ES */ }@Component class RiskControlListener { /* 通知风控 */ }@Component class AlertListener { /* 发送警报 */ }@Component class AuditListener { /* 记录日志 */ }
改造后,库存服务的核心方法从30行减少到10行,而且新增同步需求时完全不需要修改库存服务代码!
07、最佳实践与避坑指南
经过多个项目的实践,我总结了一些最佳实践:
✅ 应该这样做:
- 1. 按事件类型分离监听器:一个监听器只处理一种事件,保持单一职责
- 2. 明确执行顺序:使用
@Order注解或实现Ordered接口 - 3. 异步处理耗时操作:使用
@Async避免阻塞主流程 - 4. 做好异常隔离:单个监听器失败不应该影响其他监听器
- 5. 添加监控指标:记录监听器的执行时间、成功率等
❌ 避免这些坑:
- 1. 不要在监听器中修改事件源:事件对象应该是不可变的
- 2. 小心循环依赖:A监听B事件,B监听A事件,会导致死循环
- 3. 避免在监听器中抛异常中断流程:除非是致命错误
- 4. 注意事务边界:理解
@EventListener和@TransactionalEventListener的区别
性能调优配置:
# application.ymlspring: application: name: user-center # 事件监听相关配置 task: execution: pool: core-size: 5 max-size: 20 queue-capacity: 100 # 关闭不需要的默认事件 event: multicast: error-handler: customErrorHandler
结语:从"硬编码"到"事件驱动"的思维转变
三年前,我也是那个把所有初始化逻辑都塞在@PostConstruct方法里的程序员。直到一次线上事故——因为缓存初始化失败,导致整个用户注册流程瘫痪——我才意识到解耦的重要性。
ApplicationListener不仅仅是一个技术工具,它更代表了一种设计思想:响应式编程和事件驱动架构。当我们把系统从"命令式"改造为"响应式",代码的可维护性、可扩展性、可测试性都会得到质的提升。
下次当你发现自己在服务类里写@Autowired注入一大堆依赖,然后在方法里依次调用时,不妨停下来想一想:这里是不是可以用事件监听来解耦?
记住:优秀的架构不是一开始就设计出来的,而是在不断重构中演进出来的。而事件监听机制,就是你重构工具箱中的一把瑞士军刀。
最后留个思考题:如果你的系统有几十个监听器,启动时需要确保某些监听器必须执行成功(如数据库连接),而某些可以失败(如非关键的外部服务),你会如何设计这样的优先级和容错机制?欢迎在评论区分享你的架构思路。