当前位置:首页>java>别在业务代码里硬编码!用ApplicationListener搞定系统事件,解耦神器!

别在业务代码里硬编码!用ApplicationListener搞定系统事件,解耦神器!

  • 2026-08-26 19:35:34
别在业务代码里硬编码!用ApplicationListener搞定系统事件,解耦神器!

你是否还在业务代码里写硬编码的系统初始化逻辑?上周我重构了一个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. 1. 职责不清:一个方法做多件事,违反了单一职责原则
  2. 2. 异常处理混乱:某个步骤失败不影响其他步骤?但日志里根本看不出哪里出了问题
  3. 3. 难以扩展:想要新增一个初始化步骤,得在这个500行的方法里找地方插入
  4. 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. 1. 按事件类型分离监听器:一个监听器只处理一种事件,保持单一职责
  2. 2. 明确执行顺序:使用@Order注解或实现Ordered接口
  3. 3. 异步处理耗时操作:使用@Async避免阻塞主流程
  4. 4. 做好异常隔离:单个监听器失败不应该影响其他监听器
  5. 5. 添加监控指标:记录监听器的执行时间、成功率等

❌ 避免这些坑:

  1. 1. 不要在监听器中修改事件源:事件对象应该是不可变的
  2. 2. 小心循环依赖:A监听B事件,B监听A事件,会导致死循环
  3. 3. 避免在监听器中抛异常中断流程:除非是致命错误
  4. 4. 注意事务边界:理解@EventListener和@TransactionalEventListener的区别
  5. 5. 控制监听器数量:过多的监听器会影响启动速度

性能调优配置:

# 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注入一大堆依赖,然后在方法里依次调用时,不妨停下来想一想:这里是不是可以用事件监听来解耦?

记住:优秀的架构不是一开始就设计出来的,而是在不断重构中演进出来的。而事件监听机制,就是你重构工具箱中的一把瑞士军刀。


最后留个思考题:如果你的系统有几十个监听器,启动时需要确保某些监听器必须执行成功(如数据库连接),而某些可以失败(如非关键的外部服务),你会如何设计这样的优先级和容错机制?欢迎在评论区分享你的架构思路。

最新文章

随机文章