当前位置: 首页 > news >正文

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

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监听器能优雅地实现:

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

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

相关文章:

  • 2024华为OD机试真题-热点网站统计(C++)-E卷-100分
  • AVM 环视拼接 鱼眼相机
  • 离散傅里叶变换(Discrete Fourier Transform, DFT)及其在图像处理中的应用
  • 动态表头报表的绘制与导出
  • 内网穿透的应用-企业级远程办公方案:NAS部署网页版Linux,HTTPS加密访问全配置
  • Ubuntu 创建新用户及设置权限
  • SSH远程登录并执行命令
  • 工程化与框架系列(10)--微前端架构
  • springboot使用redis
  • 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案
  • 技术速递|增强 Razor 生产力的新功能!
  • Redis数据结构详解
  • Spring Boot 中 RabbitMQ 的使用
  • 【前端基础】3、HTML的常用元素(h、p、img、a、iframe、div、span)、不常用元素(strong、i、code、br)
  • (转)Java中collection和 collections区别
  • Linux切换Python版本
  • MaxKB上架至阿里云轻量应用服务器镜像市场
  • 构建智能 SQL 查询代理agent,把整个查询过程模块化,既能自动判断使用哪些表,又能自动生成 SQL 语句,最终返回查询结果
  • LeetCode 79: 单词搜索 (Word Search)
  • 基础篇:Linux安装redis教程(详细)
  • 郑州通报“夜市摊贩收取香烟交给城管”:涉事人员停职调查
  • 宝妈称宝宝在粽子中吃出带血创可贴,来伊份:已内部排查
  • 姚洋将全职加盟上海财经大学,担任滴水湖高级金融学院院长
  • 宇树科技王兴兴:第一桶金来自上海,欢迎上海的年轻人加入
  • 李在明正式登记参选下届韩国总统
  • 近4小时会谈、3项联合声明、20多份双边合作文本,中俄元首今年首次面对面会晤成果颇丰