Spring Boot 监听器(Listeners)详细教程

发布于:2025-03-08 ⋅ 阅读:(115) ⋅ 点赞:(0)

Spring Boot 监听器(Listeners)详细教程


目录

  1. Spring Boot 监听器概述
  2. 监听器核心概念
  3. 最佳使用场景
  4. 实现步骤
  5. 高级配置
  6. 详细使用场景
  7. 总结

1. Spring Boot 监听器概述

Spring Boot 监听器(Listeners)基于 Spring Framework 的事件机制(ApplicationEventApplicationListener),用于在应用生命周期或自定义事件触发时执行特定逻辑。它们提供了一种松耦合的方式响应应用状态变化,常用于初始化资源、监控应用状态、执行异步任务等。

2. 核心概念

2.1 事件类型

  • 内置系统事件
    • ContextRefreshedEvent:ApplicationContext初始化或刷新时触发
    • ContextStartedEvent:ApplicationContext启动后触发
    • ContextStoppedEvent:ApplicationContext停止后触发
    • ContextClosedEvent:ApplicationContext关闭后触发
    • ApplicationStartedEvent:Spring Boot应用启动后触发
    • ApplicationReadyEvent:应用准备就绪时触发(推荐在此执行启动逻辑)
    • ApplicationFailedEvent:启动失败时触发
  • 自定义事件:继承ApplicationEvent创建特定业务事件

2.2 监听器类型

  • 接口实现:实现ApplicationListener<EventType>
  • 注解驱动:使用@EventListener注解方法
  • SmartApplicationListener:支持事件类型过滤和顺序控制

简单说就是:

  • 事件(Event):继承 ApplicationEvent 的类,表示一个事件(如应用启动、关闭等)。
  • 监听器(Listener):实现 ApplicationListener 接口或使用 @EventListener 注解的组件,用于响应事件。
  • 事件发布(Publisher):通过 ApplicationEventPublisher 发布事件。

3. 最佳使用场景

场景 说明
应用生命周期管理 在应用启动、关闭时初始化或释放资源(如数据库连接、线程池)。
异步任务触发 通过事件驱动异步处理(如发送邮件、记录日志)。
业务逻辑解耦 模块间通过事件通信,避免直接依赖。业务事件处理(订单创建通知、日志审计)
监控与统计 监听请求事件统计 API 调用次数、响应时间等。

4. 实现步骤(代码示例)

4.1 系统事件监听

方式1:实现ApplicationListener接口
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

public class SystemStartupListener implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("=== 应用启动完成,执行初始化操作 ===");
        // 初始化业务数据...
    }
}
方式2:使用@EventListener注解
import org.springframework.context.event.EventListener;
import org.springframework.boot.context.event.ApplicationStartedEvent;

@Component
public class AnnotationBasedListener {
    
    @EventListener
    public void handleStartedEvent(ApplicationStartedEvent event) {
        System.out.println("=== 应用启动事件捕获 ===");
    }
}

4.2 自定义事件

步骤1:定义事件类
public class OrderCreateEvent extends ApplicationEvent {
    private String orderId;

    public OrderCreateEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}
步骤2:发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void createOrder(Order order) {
        // 创建订单逻辑...
        eventPublisher.publishEvent(new OrderCreateEvent(this, order.getId()));
    }
}
步骤3:监听事件
@Component
public class OrderEventListener {
    
    @EventListener
    public void handleOrderEvent(OrderCreateEvent event) {
        System.out.println("收到订单创建事件,订单ID:" + event.getOrderId());
        // 发送通知、更新统计...
    }
}

5. 高级配置

5.1 监听器顺序控制

@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级
public void handleEventFirst(MyEvent event) {
    // 最先执行
}

5.2 异步事件处理

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.initialize();
        return executor;
    }
}

@EventListener
@Async
public void asyncHandleEvent(MyEvent event) {
    // 异步执行
}

5.3 条件过滤

@EventListener(condition = "#event.orderId.startsWith('VIP')")
public void handleVipOrder(OrderCreateEvent event) {
    // 只处理VIP订单
}

6.详细使用场景


场景1:应用启动时缓存预热(系统事件监听)

需求描述
在应用启动完成后,自动加载热门商品数据到Redis缓存,提升接口响应速度。

@Component
public class CacheWarmUpListener {

    private final ProductService productService;
    private final RedisTemplate<String, Product> redisTemplate;

    @Autowired
    public CacheWarmUpListener(ProductService productService, 
                              RedisTemplate<String, Product> redisTemplate) {
        this.productService = productService;
        this.redisTemplate = redisTemplate;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void warmUpCache() {
        List<Product> hotProducts = productService.getTop100HotProducts();
        hotProducts.forEach(product -> 
            redisTemplate.opsForValue().set("product:" + product.getId(), product));
      
        System.out.println("=== 已预热" + hotProducts.size() + "条商品数据到Redis ===");
    }
}

关键点说明

  • 使用ApplicationReadyEvent而非ApplicationStartedEvent,确保数据库连接等基础设施已就绪
  • 通过构造函数注入依赖,避免字段注入的循环依赖问题
  • 预热数据量较大时建议采用分页异步加载

场景2:订单创建后发送多平台通知(自定义事件)

需求描述
当订单创建成功后,需要同时发送短信通知用户、邮件通知客服、更新ERP系统库存。

步骤1:定义自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
  
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
  
    public Order getOrder() {
        return order;
    }
}
步骤2:在Service中发布事件
@Service
public class OrderService {

    private final ApplicationEventPublisher eventPublisher;

    @Autowired
    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Transactional
    public Order createOrder(OrderCreateRequest request) {
        Order newOrder = // 创建订单的数据库操作...
        eventPublisher.publishEvent(new OrderCreatedEvent(this, newOrder));
        return newOrder;
    }
}
步骤3:多监听器处理事件
@Component
public class OrderNotificationListener {

    // 短信通知(最高优先级)
    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public void sendSms(OrderCreatedEvent event) {
        Order order = event.getOrder();
        SmsService.send(order.getUserPhone(), 
            "您的订单#" + order.getId() + "已创建,金额:" + order.getAmount());
    }

    // 邮件通知(异步处理)
    @Async
    @EventListener
    public void sendEmail(OrderCreatedEvent event) {
        Order order = event.getOrder();
        EmailTemplate template = EmailTemplate.buildOrderConfirm(order);
        EmailService.send(template);
    }

    // ERP系统库存更新(条件过滤)
    @EventListener(condition = "#event.order.items.?[isPhysicalProduct].size() > 0")
    public void updateErpInventory(OrderCreatedEvent event) {
        ERPInventoryService.updateStock(event.getOrder().getItems());
    }
}

配置异步支持

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "notificationTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Notification-");
        executor.initialize();
        return executor;
    }
}

优势

  • 解耦核心业务与通知逻辑
  • 通过@Order控制短信优先于邮件发送
  • 使用@Async避免邮件发送阻塞主线程
  • 条件表达式跳过虚拟商品库存更新

场景3:全局请求耗时统计(ServletRequestListener)

需求描述
统计所有API请求的处理时间,识别慢接口。

@Component
public class RequestMetricsListener implements ServletRequestListener {

    private static final ThreadLocal<Long> startTimeHolder = new ThreadLocal<>();

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        startTimeHolder.set(System.currentTimeMillis());
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        long startTime = startTimeHolder.get();
        long duration = System.currentTimeMillis() - startTime;
      
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String endpoint = request.getRequestURI();
        String method = request.getMethod();
      
        MetricsService.recordRequestMetrics(endpoint, method, duration);
      
        // 慢请求预警
        if(duration > 3000) {
            AlarmService.notifySlowRequest(endpoint, method, duration);
        }
      
        startTimeHolder.remove();
    }
}

注册监听器

@Bean
public ServletListenerRegistrationBean<RequestMetricsListener> metricsListener() {
    return new ServletListenerRegistrationBean<>(new RequestMetricsListener());
}

统计结果示例

GET /api/products 平均耗时 45ms | 95分位 120ms
POST /api/orders 平均耗时 250ms | 最大耗时 3200ms(需优化)

场景4:应用优雅停机(ContextClosedEvent)

需求描述
在应用关闭时,确保完成:1)停止接收新请求 2)等待进行中的任务完成 3)释放资源。

@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {

    private final ThreadPoolTaskExecutor taskExecutor;
    private final DataSource dataSource;

    @Autowired
    public GracefulShutdownListener(ThreadPoolTaskExecutor taskExecutor, 
                                   DataSource dataSource) {
        this.taskExecutor = taskExecutor;
        this.dataSource = dataSource;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 1. 关闭线程池
        shutdownExecutor(taskExecutor);

        // 2. 关闭数据库连接池
        if(dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }

        // 3. 其他清理工作...
        System.out.println("=== 资源释放完成,应用安全退出 ===");
    }

    private void shutdownExecutor(ExecutorService executor) {
        executor.shutdown();
        try {
            if(!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

停机流程

  1. 收到SIGTERM信号
  2. 关闭新请求入口
  3. 等待30秒处理进行中请求
  4. 强制关闭剩余任务
  5. 释放数据库连接池
  6. 应用退出

场景5:分布式锁异常恢复

需求描述
当获取Redis分布式锁失败时,触发重试机制并记录竞争情况。

自定义事件
public class LockAcquireFailedEvent extends ApplicationEvent {
    private final String lockKey;
    private final int retryCount;

    public LockAcquireFailedEvent(Object source, String lockKey, int retryCount) {
        super(source);
        this.lockKey = lockKey;
        this.retryCount = retryCount;
    }

    // getters...
}
事件发布
public class DistributedLock {

    private final ApplicationEventPublisher eventPublisher;

    public boolean tryLock(String key, int maxRetries) {
        int attempts = 0;
        while(attempts < maxRetries) {
            if(RedisClient.acquireLock(key)) {
                return true;
            }
            attempts++;
            eventPublisher.publishEvent(new LockAcquireFailedEvent(this, key, attempts));
            Thread.sleep(100 * attempts);
        }
        return false;
    }
}
监听处理
@Component
public class LockFailureHandler {

    private static final Map<String, AtomicInteger> LOCK_CONTENTION = new ConcurrentHashMap<>();

    @EventListener
    public void handleLockFailure(LockAcquireFailedEvent event) {
        String lockKey = event.getLockKey();
        LOCK_CONTENTION.computeIfAbsent(lockKey, k -> new AtomicInteger(0))
                      .incrementAndGet();
      
        // 竞争激烈时动态调整策略
        if(event.getRetryCount() > 3) {
            adjustBackoffStrategy(lockKey);
        }
    }

    @Scheduled(fixedRate = 10_000)
    public void reportContention() {
        LOCK_CONTENTION.forEach((key, count) -> 
            MetricsService.recordLockContention(key, count.get()));
    }

    private void adjustBackoffStrategy(String key) {
        // 动态增加等待时间或告警
    }
}

监控面板显示

订单库存锁竞争次数:142次/分钟 → 建议拆分锁粒度
优惠券发放锁竞争:23次/分钟 → 正常范围

最佳实践总结

  1. 事件选择原则

    • 系统生命周期:优先使用ApplicationReadyEvent而非ContextRefreshedEvent
    • 业务事件:根据领域模型设计细粒度事件
  2. 性能优化

    • 耗时操作使用@Async+线程池
    • 高频事件考虑批量处理
  3. 错误处理

    @EventListener
    public void handleEvent(MyEvent event) {
        try {
            // 业务逻辑
        } catch (Exception e) {
            ErrorTracker.track(e);
            // 决定是否重新抛出
        }
    }
    
  4. 测试策略

    @SpringBootTest
    class OrderEventTest {
      
        @Autowired
        private ApplicationEventPublisher publisher;
      
        @Test
        void testOrderNotification() {
            Order mockOrder = createTestOrder();
            publisher.publishEvent(new OrderCreatedEvent(this, mockOrder));
          
            // 验证短信、邮件发送记录
        }
    }
    

7.总结

通过以上场景可以看出,Spring Boot监听器能优雅地实现:

  • 系统层的资源生命周期管理
  • 业务层的事件驱动架构
  • 运维层的监控预警机制
  • 架构层的解耦与扩展

实际开发中应根据业务复杂度选择合适的事件策略,平衡灵活性与维护成本。


网站公告

今日签到

点亮在社区的每一天
去签到