Spring Boot 监听器(Listeners)详细教程
Spring Boot 监听器(Listeners)详细教程
目录
- Spring Boot 监听器概述
- 监听器核心概念
- 最佳使用场景
- 实现步骤
- 高级配置
- 详细使用场景
- 总结
1. Spring Boot 监听器概述
Spring Boot 监听器(Listeners)基于 Spring Framework 的事件机制(ApplicationEvent
和 ApplicationListener
),用于在应用生命周期或自定义事件触发时执行特定逻辑。它们提供了一种松耦合的方式响应应用状态变化,常用于初始化资源、监控应用状态、执行异步任务等。
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();}}
}
停机流程:
- 收到SIGTERM信号
- 关闭新请求入口
- 等待30秒处理进行中请求
- 强制关闭剩余任务
- 释放数据库连接池
- 应用退出
场景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次/分钟 → 正常范围
最佳实践总结
-
事件选择原则:
- 系统生命周期:优先使用
ApplicationReadyEvent
而非ContextRefreshedEvent
- 业务事件:根据领域模型设计细粒度事件
- 系统生命周期:优先使用
-
性能优化:
- 耗时操作使用
@Async
+线程池 - 高频事件考虑批量处理
- 耗时操作使用
-
错误处理:
@EventListener public void handleEvent(MyEvent event) {try {// 业务逻辑} catch (Exception e) {ErrorTracker.track(e);// 决定是否重新抛出} }
-
测试策略:
@SpringBootTest class OrderEventTest {@Autowiredprivate ApplicationEventPublisher publisher;@Testvoid testOrderNotification() {Order mockOrder = createTestOrder();publisher.publishEvent(new OrderCreatedEvent(this, mockOrder));// 验证短信、邮件发送记录} }
7.总结
通过以上场景可以看出,Spring Boot监听器能优雅地实现:
- 系统层的资源生命周期管理
- 业务层的事件驱动架构
- 运维层的监控预警机制
- 架构层的解耦与扩展
实际开发中应根据业务复杂度选择合适的事件策略,平衡灵活性与维护成本。
相关文章:
Spring Boot 监听器(Listeners)详细教程
Spring Boot 监听器(Listeners)详细教程 目录 Spring Boot 监听器概述监听器核心概念最佳使用场景实现步骤高级配置详细使用场景总结 1. Spring Boot 监听器概述 Spring Boot 监听器(Listeners)基于 Spring Framework 的事件机制…...
工具介绍《githack》以及Git 命令行
一、Githack 工具介绍 Githack 是一个用于检测和利用网站 .git 目录泄露漏洞的安全工具。当网站错误配置导致 .git 目录可公开访问时,攻击者可通过该工具下载 .git 中的版本控制文件,并重建完整的项目源代码。 核心用途 检测 .git 目录泄露漏洞。从泄…...

【hello git】git rebase、git merge、git stash、git cherry-pick
目录 一、git merge:保留了原有分支的提交结构 二、git rebase:提交分支更加整洁 三、git stash 四、git cherry-pick 共同点:将 一个分支的提交 合并到 到另一个上分支上去 一、git merge:保留了原有分支的提交结构 现有一个模型…...

MR的环形缓冲区(底层)
MapReduce的大致流程: 1、HDFS读取数据; 2、按照规则进行分片,形成若干个spilt; 3、进行Map 4、打上分区标签(patition) 5、数据入环形缓冲区(KVbuffer) 6、原地排序ÿ…...

下载Hugging Face模型的几种方式
1.网页下载 直接访问Hugging Face模型页面,点击“File and versions”选项卡,选择所需的文件进行下载。 2.使用huggingface-cli 首先,安装huggingface_hub: pip install huggingface_hub 然后,使用以下命令下载模型࿱…...

Java 第十一章 GUI编程(2)
目录 GUI 事件处理 基本思路 添加事件监听器 对话框 实例 GUI 事件处理 对于采用了图形用户界面的程序来说,事件控制是非常重要的;到目前为止, 我们编写的图形用户界面程序都仅仅只是完成了界面,而没有任何实际的功能&…...
Redis数据结构深度解析:从String到Stream的奇幻之旅(一)
Redis系列文章 《半小时掌握Redis核心操作:从零开始的实战指南》-CSDN博客 Redis数据结构深度解析:从String到Stream的奇幻之旅(一)-CSDN博客 Redis数据结构深度解析:从String到Stream的奇幻之旅(二&…...

7V 至 30V 的超宽 VIN 输入范围,转换效率高达 96%的WD5030
WD5030 具备 7V 至 30V 的超宽 VIN 输入范围,这一特性使其能够适应多种不同电压等级的供电环境,无论是在工业设备中常见的较高电压输入,还是在一些便携式设备经过初步升压后的电压,WD5030 都能轻松应对,极大地拓展了应…...

【Git原理与使用一】Git概念与基本操作
文章目录 1. Git 的概念2. Git 的安装3. Git 的认识3.1 创建本地仓库3.2 配置Git3.3 认识工作区、暂存区、版本库 4. Git 的基本操作4.1、认识几个指令1)git add 添加命令2)git commit 提交命令3)git log 查看日志命令4)git cat-f…...

kettle工具使用从入门到精通(一)
安装 可以从链接: 官网(下载链接在Pentaho.pdf文件里)或者网络上查找对应的版本安装 Kettle (PDI) 版本与 JDK 版本对应关系 Kettle (PDI) 版本支持的 JDK 版本备注PDI 9.x 及以上JDK 11 或更高版本推荐使用 OpenJDK 或 Oracle JDK 11。PDI 8.xJDK 8 …...
Java 实现 Oracle 的 MONTHS_BETWEEN 函数
介绍 因为系统迁移, 有一些函数要转成 Java 版本, Oracle 的 官方介绍 - MONTHS_BETWEEN MONTHS_BETWEEN returns number of months between dates date1 and date2. The month and the last day of the month are defined by the parameter NLS_CALENDAR. If date1 is late…...

windows下使用msys2编译ffmpeg
三种方法: 1、在msys2中使用gcc编译 2、在msys2中使用visual studio编译(有环境变量) 3、在msys2中使用visual studio编译(无环境变量) 我的环境: 1、msys2-x86_64-20250221 2、vs2015 3、ffmpeg-7.1…...
Vivado常用的时序约束方法
1,create_clock :创建时钟约束 create_clock -period 20.000 -name sys_clk [get_ports sys_clk 该约束含义是创建一个时钟周期20ns的时钟,时钟名字为sys_clk。注意:如果是差分时钟,只需要约束差分时钟的P端,N端不用约束。 2,set_clock_uncertainty:设置时钟不确定性 s…...

力扣HOT100之哈希:1. 两数之和
这道题之前刷代码随想录的时候已经刷过好几遍了,看到就直接秒了。这道题主要是通过unordered_map<int, int>来建立哈希表,其中键用来保存向量中的元素,而对应的值则为元素的下标。遍历整个向量,当遍历到nums[i]时࿰…...
如何在rust中解析 windows 的 lnk文件(快捷方式)
一、从标题二开始看😁 这些天在使用rust写一个pc端应用程序,需要解析lnk文件获取lnk的图标以及原程序地址,之前并没有过pc端应用程序开发的经验, 所以在广大的互联网上游荡了两天。额🥺 今天找到了这个库 lnk_parse很…...

豆包大模型 MarsCode AI 刷题专栏 001
001.找单独的数 难度:易 问题描述 在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小C快速找到那个拿了独特数字卡片的同学手上…...

python语言总结(持续更新)
本文主要是总结各函数,简单的函数不会给予示例,如果在平日遇到一些新类型将会添加 基础知识 输入与输出 print([要输出的内容])输出函数 input([提示内容]如果输入提示内容会在交互界面显示,用以提示用户)输入函数 注释 # 单行注释符&…...
leetcode15 三数之和
1.哈希法 为了避免重复 class Solution { public:vector<vector<int>> threeSum(vector<int>& nums) {set<vector<int>> temple;//使用 set 来存储符合条件的三元组,避免重复vector<vector<int>> out;//存放最终输…...
深入探讨AI-Ops架构 第一讲 - 运维的进化历程以及未来发展趋势
首先,让我们一起回顾运维的进化之路,然后再深入探讨AI-Ops架构的细节。 运维的进化历程 1. AI 大范围普及前的运维状态 (传统运维) 在AI技术尚未广泛渗透到运维领域之前,我们称之为传统运维,其主要特点是: 人工驱动…...

Android Native 之 文件系统挂载
一、文件系统挂载流程概述 二、文件系统挂载流程细节 1、Init启动阶段 众所周知,init进程为android系统的第一个进程,也是native世界的开端,要想让整个android世界能够稳定的运行,文件系统的创建和初始化是必不可少的ÿ…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
OCR MLLM Evaluation
为什么需要评测体系?——背景与矛盾 能干的事: 看清楚发票、身份证上的字(准确率>90%),速度飞快(眨眼间完成)。干不了的事: 碰到复杂表格(合并单元…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...

【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...