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

ops/2025/3/5 14:01:47/

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> {@Overridepublic 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 {@EventListenerpublic 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 {@Autowiredprivate ApplicationEventPublisher eventPublisher;public void createOrder(Order order) {// 创建订单逻辑...eventPublisher.publishEvent(new OrderCreateEvent(this, order.getId()));}
}
步骤3:监听事件
@Component
public class OrderEventListener {@EventListenerpublic 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 {@Overridepublic 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;@Autowiredpublic 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;@Autowiredpublic OrderService(ApplicationEventPublisher eventPublisher) {this.eventPublisher = eventPublisher;}@Transactionalpublic 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@EventListenerpublic 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<>();@Overridepublic void requestInitialized(ServletRequestEvent sre) {startTimeHolder.set(System.currentTimeMillis());}@Overridepublic 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;@Autowiredpublic GracefulShutdownListener(ThreadPoolTaskExecutor taskExecutor, DataSource dataSource) {this.taskExecutor = taskExecutor;this.dataSource = dataSource;}@Overridepublic 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<>();@EventListenerpublic 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 {@Autowiredprivate ApplicationEventPublisher publisher;@Testvoid testOrderNotification() {Order mockOrder = createTestOrder();publisher.publishEvent(new OrderCreatedEvent(this, mockOrder));// 验证短信、邮件发送记录}
    }
    

7.总结

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

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

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


http://www.ppmy.cn/ops/163319.html

相关文章

Mac远程桌面软件哪个好用?

远程桌面软件能帮助我们快速的远程控制另一台电脑&#xff0c;从而提供远程帮助&#xff0c;或者进行远程办公。那么&#xff0c;对macOS系统有什么好用的Mac远程桌面软件呢&#xff1f; 远程看看是一款操作简单、界面简洁的远程桌面软件&#xff0c;支持跨平台操作&#xff0…

2025年03月04日Github流行趋势

项目名称&#xff1a;ladybird 项目地址url&#xff1a;https://github.com/LadybirdBrowser/ladybird项目语言&#xff1a;C历史star数&#xff1a;33403今日star数&#xff1a;1726项目维护者&#xff1a;awesomekling, trflynn89, linusg, AtkinsSJ, alimpfard项目简介&…

P10904 [蓝桥杯 2024 省 C] 挖矿

P10904 [蓝桥杯 2024 省 C] 挖矿 题目描述 小蓝正在数轴上挖矿&#xff0c;数轴上一共有 n n n 个矿洞&#xff0c;第 i i i 个矿洞的坐标为 a i a_i ai​。小蓝从 0 0 0 出发&#xff0c;每次可以向左或向右移动 1 1 1 的距离&#xff0c;当路过一个矿洞时&#xff0c;…

Spring原理

目录 Bean的作用域 Bean的作用域的介绍 Bean有哪些作用域 Bean的生命周期 Spring boot的自动配置 原因 解决方案 通过ComponentScan注解扫描 Import导入类 ImportSelector接口实现类 原理分析 EnableAutoConfiguration Import(AutoConfigurationImportSelector.cla…

DBeaver下载安装及数据库连接(MySQL)

1. DBeaver下载 官网下载地址:Download | DBeaver Community 2. 安装 1. 双击下载的安装包&#xff0c;选择简体中文。 2. 点击下一步。 3. 点击我接受。 4. 如下勾选为所有用户安装&#xff0c;点击下一步。 5. 需重复做1~3 的步骤。 6. 选择组件&#xff0c;默认即可&…

3D手眼标定转换详细实施步骤及原理概述

3D手眼标定转换详细实施步骤及原理概述 一、手眼标定的核心目标二、3D手眼标定的原理概述一、基本概念与坐标系定义**二、数学建模与方程推导****1. 坐标变换的齐次矩阵表示****2. 手眼标定方程推导** **三、方程求解方法****1. 分离旋转与平移****2. 旋转矩阵求解****3. 平移向…

Linux : 环境变量

目录 一 环境变量 1.基本概念 二 常见环境变量 三 查看环境变量的方法 1.env:查看系统中所有环境变量 2. echo $NAME 四 如何不带路径也能运行的自己的程序 1.将自己的程序直接添加到PATH指定的路径下 五 环境变量与本地变量 1.本地变量 2. 环境变量 六C、C中main()…