浅浅了解下Spring中生命周期函数(Spring6全攻略)
你好,这里是codetrend专栏“Spring6全攻略”。
Spring框架设计生命周期回调函数的主要目的是为了提供一种机制,使开发人员能够在对象创建、初始化和销毁等生命周期阶段执行特定的操作。这种机制可以帮助开发人员编写更加灵活和可维护的代码。
举个例子。
缓存预热是一种在程序启动或缓存失效之后,主动将热点数据加载到缓存中的策略。
通过缓存预热能避免第一次查询数据慢的问题。
那如何在应用启动的时候把数据全量写入缓存这呢?
这个时候就可以用到Spring的生命周期函数。
在服务创建的时候写一个init函数,加上注解@PostConstruct之后,就会在应用启动的时候调用。
这样一旦数据没有在缓存,就会二次写入。
整个过程用mermaid表示如下:
生命周期函数有哪些使用场景
Spring框架的生命周期回调函数有多种使用场景,以下是一些常见的情况:
- 初始化资源:在Bean初始化之后,可能需要进行一些资源的初始化操作,比如建立数据库连接、加载配置信息等。通过初始化回调函数,可以在Bean准备就绪后执行这些操作。
- 释放资源:在Bean销毁之前,可能需要进行一些资源的释放操作,比如关闭数据库连接、释放文件句柄等。通过销毁回调函数,可以在Bean即将被销毁时执行这些清理操作。
- 依赖注入后的处理:有时候在依赖注入完成后需要执行特定的逻辑,例如根据依赖的情况进行一些动态调整或者校验。
- 与外部系统集成:在与外部系统集成时,可能需要在Bean创建后或销毁前执行一些初始化或清理工作,例如注册到消息队列、向外部服务发送初始化请求等。
- 日志记录:使用生命周期回调函数可以方便地记录Bean的创建、初始化和销毁等生命周期事件,以便进行调试和排查问题。
- 定时任务:通过生命周期回调函数可以实现定时任务的启动和关闭,例如在应用启动时启动定时任务,在应用关闭时停止定时任务。
有哪些生命周期回调
默认的回调函数有如下几种:
- 初始化回调:在Bean对象实例化后、属性注入完成之后,执行特定的初始化操作的过程。
- 销毁回调:在Bean对象即将被销毁前执行特定的清理操作的过程。
- 启动和停止回调:在整个Spring应用程序上下文启动和停止时执行的回调方法。
除此之外还可以通过实现接口BeanPostProcessor来完成任意的回调函数。
初始化回调
在Spring中,Bean的初始化回调可以通过实现InitializingBean接口、@PostConstruct注解或在XML配置中使用init-method来实现。下面将详细说明各种方式的用法,并举例说明。
实现InitializingBean接口:
- 实现InitializingBean接口的类需要实现afterPropertiesSet()方法,在该方法中编写初始化逻辑。
- 示例代码如下:
@Slf4j
class MovieFinder implements InitializingBean {public List<String> findMovies() {return Arrays.asList("电影1", "电影2", "电影3");}@Overridepublic void afterPropertiesSet() throws Exception {log.info("电影数据初始化中...");}
}
使用@PostConstruct注解:
- 使用javax.annotation.PostConstruct注解标记一个方法作为初始化方法,在依赖注入完成后会自动调用该方法。
- 把上面的代码稍微改造下,示例代码如下:
@Slf4j
class MovieFinder implements InitializingBean {public List<String> findMovies() {return Arrays.asList("电影1", "电影2", "电影3");}@Overridepublic void afterPropertiesSet() throws Exception {log.info("电影数据初始化中...");}@PostConstructpublic void init() {// 初始化逻辑log.info("电影数据初始化中...通过PostConstruct");}
}
XML配置init-method:
- 在XML配置中,可以通过init-method属性指定Bean的初始化方法,在Bean实例化后会调用该方法。
- XML配置示例:
<bean id="myBean" class="com.example.MyBean" init-method="init">
</bean>
public class MyBean {public void init() {// 初始化逻辑System.out.println("MyBean is being initialized.");}
}
源码分析:
Spring的调用链路很长,按顺序执行的方法如下:
AbstractAutowireCapableBeanFactory#createBeanAbstractAutowireCapableBeanFactory#doCreateBeanAbstractAutowireCapableBeanFactory#doCreateBeanAbstractAutowireCapableBeanFactory#initializeBeanAbstractAutowireCapableBeanFactory#invokeInitMethods
doCreateBean 调用了两个核心函数,其中第二个就是初始化函数。
// 给bean的属性设置一些逻辑
populateBean(beanName, mbd, instanceWrapper);
// 初始化逻辑,这块就是执行初始化回调的地方
exposedObject = initializeBean(beanName, exposedObject, mbd);
其中初始化的核心代码就是这段。
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)throws Throwable {// 解析实现了InitializingBean,也就是调用afterPropertiesSetboolean isInitializingBean = (bean instanceof InitializingBean);if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) {if (logger.isTraceEnabled()) {logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");}((InitializingBean) bean).afterPropertiesSet();}// 解析各种初始化方法,自定义的、注解注入的if (mbd != null && bean.getClass() != NullBean.class) {String[] initMethodNames = mbd.getInitMethodNames();if (initMethodNames != null) {for (String initMethodName : initMethodNames) {if (StringUtils.hasLength(initMethodName) &&!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.hasAnyExternallyManagedInitMethod(initMethodName)) {invokeCustomInitMethod(beanName, bean, mbd, initMethodName);}}}}}
销毁回调
@PreDestroy 注解
- 功能:允许开发者通过注解标记 Bean 销毁时应执行的方法。
- 优点:简单直观,符合 Java 标准,易于使用。
- 使用场景:适用于需要在 Bean 销毁前执行一些清理操作,如关闭资源等。
实现 DisposableBean 接口
- 功能:提供了一个回调接口,要求实现
destroy方法来处理 Bean 销毁时的逻辑。 - 优点:接口方式,强制性较强,适合需要明确销毁逻辑的场景。
- 使用场景:适用于需要在 Bean 销毁前执行复杂操作或依赖其他 Spring Bean 的情况。
自定义销毁方法:
- 功能:允许在配置类中指定 Bean 的销毁方法。
- 优点:灵活性高,方法名可以自由定义。
- 使用场景:适用于需要灵活配置的 Bean 销毁逻辑,尤其是通过 Java 配置类定义 Bean 的情况。
Bean代码如下:
/*** 服务代码*/
@Slf4j
class SimpleMovieLister implements DisposableBean {private final MovieFinder movieFinder;public SimpleMovieLister(MovieFinder movieFinder) {this.movieFinder = movieFinder;}public void listMovies() {log.info("电影列表打印中");movieFinder.findMovies().forEach(log::info);}@PreDestroypublic void onDestroy() {log.info("Bean is being destroyed");}@Overridepublic void destroy() throws Exception {log.info("DisposableBean is being destroyed");}public void customDestroy() {log.info("Custom destroy method is being called");}
}
APP配置如下:
/*** App配置*/
@Configuration
class ConstructorAppConfig{@Beanpublic MovieFinder movieFinder() {return new MovieFinder();}// destroyMethod属性能指定自定义属性@Bean(destroyMethod = "customDestroy")public SimpleMovieLister simpleMovieLister(MovieFinder movieFinder) {return new SimpleMovieLister(movieFinder);}
}
解析销毁方法需要 CommonAnnotationBeanPostProcessor,这里就在启动类手动注入了对应的处理器。想要触发还需要手动close对应的bean工厂。
/*** bean生命周期自定义* @author nine* @since 1.0*/
@Slf4j
public class BeanLifeCycleDemo {public static void main(String[] args) {// 创建一个基于 Java Config 的应用上下文AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConstructorAppConfig.class);// @Resource @PostConstruct @PreDestroycontext.registerBean(CommonAnnotationBeanPostProcessor.class);for (String beanDefinitionName : context.getBeanDefinitionNames()) {log.info("beanDefinitionName: {}", beanDefinitionName);}log.info("bean初始化完成");// 从上下文中获取名bean,其类型为PetStoreServiceSimpleMovieLister bean = context.getBean(SimpleMovieLister.class);// 调用获取的bean的方法bean.listMovies();// 销毁容器context.close();}
}
相关源码DefaultSingletonBeanRegistry#destroyBean片段如下:
/*** 销毁指定名称的 bean 及其相关处理* @param beanName 要销毁的 bean 的名称* @param bean 可销毁的 bean 对象(可能为 null)*/
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {// 首先触发依赖该 bean 的其他 bean 的销毁...Set<String> dependencies;synchronized (this.dependentBeanMap) { // 在完全同步块内以确保获取到独立的集合dependencies = this.dependentBeanMap.remove(beanName);}if (dependencies!= null) {if (logger.isTraceEnabled()) {logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);}for (String dependentBeanName : dependencies) {destroySingleton(dependentBeanName); // 销毁依赖的单例}}// 现在实际销毁该 bean...if (bean!= null) {try {bean.destroy(); // 调用可销毁 bean 的销毁方法}catch (Throwable ex) {if (logger.isWarnEnabled()) {logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", ex);}}}// 触发包含在该 bean 中的 bean 的销毁...Set<String> containedBeans;synchronized (this.containedBeanMap) { // 在完全同步块内以确保获取到独立的集合containedBeans = this.containedBeanMap.remove(beanName);}if (containedBeans!= null) {for (String containedBeanName : containedBeans) {destroySingleton(containedBeanName); // 销毁包含的单例}}// 从其他 bean 的依赖中移除已销毁的 bean。synchronized (this.dependentBeanMap) {for (Iterator<Map.Entry<String, Set<String>>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) {Map.Entry<String, Set<String>> entry = it.next();Set<String> dependenciesToClean = entry.getValue();dependenciesToClean.remove(beanName);if (dependenciesToClean.isEmpty()) {it.remove();}}}// 移除已销毁 bean 的预准备依赖信息。this.dependenciesForBeanMap.remove(beanName);
}
可以看到Spring 会在 Bean 销毁时调用 destroy 方法。
启动和关闭回调
在 Spring 框架中,Startup 和 Shutdown Callbacks 提供了在容器启动和关闭时执行特定操作的功能。
Startup Callbacks(启动回调):
- 允许开发者在 Spring 应用程序启动时执行特定的操作,如初始化缓存、启动定时任务等。
- 这些回调方法通常与 Bean 的初始化相关联,在容器启动后执行。
Shutdown Callbacks(关闭回调):
- 允许开发者在 Spring 应用程序关闭时执行特定的操作,如释放资源、关闭连接等。
- 这些回调方法通常与 Bean 的销毁相关联,在容器关闭前执行。
Spring 框架实现了这一功能通过以下几个关键点:
SmartLifecycle 接口
- Spring 提供了
SmartLifecycle接口,允许 Bean 实现该接口以自定义它们的启动和关闭逻辑。实现了该接口的 Bean 在容器启动和关闭时会被自动调用。
实现 SmartLifecycle 接口:
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;@Component
public class MyLifecycleBean implements SmartLifecycle {private boolean isRunning = false;@Overridepublic void start() {System.out.println("Bean is starting...");isRunning = true;}@Overridepublic void stop() {System.out.println("Bean is stopping...");isRunning = false;}@Overridepublic boolean isRunning() {return isRunning;}
}
在Bean工厂运行的时候就会触发对应的生命周期函数。
关于作者
来自一线全栈程序员nine的探索与实践,持续迭代中。
欢迎关注或者点个收藏~
相关文章:
浅浅了解下Spring中生命周期函数(Spring6全攻略)
你好,这里是codetrend专栏“Spring6全攻略”。 Spring框架设计生命周期回调函数的主要目的是为了提供一种机制,使开发人员能够在对象创建、初始化和销毁等生命周期阶段执行特定的操作。这种机制可以帮助开发人员编写更加灵活和可维护的代码。 举个例子…...
建议收藏!亚马逊卖家必须知道的37个常用术语解释
运营亚马逊,经常会看到很多个专业术语,想必大部分新手卖家都比较陌生,熟悉这些常用术语的含义有助于你更好地运营亚马逊。下面为各位整理了37个在亚马逊跨境电商中常见的术语及其解释,建议收藏! 1、SKU Stock Keeping…...
黑苹果睡眠总是自动唤醒(RTC)
黑苹果睡眠总是自动唤醒【RTC】 1. 问题2. 解决方案2.1. 查看重启日志2.2. 配置Disable RTC wake scheduling补丁 3. 后续4. 参考 1. 问题 黑苹果EFI 更换后,总是在手动 睡眠后,间歇性重启,然后再次睡眠,然后再重启。原因归结为&…...
【代码随想录训练营】【Day 49+】【动态规划-8】| Leetcode 121, 122, 123
【代码随想录训练营】【Day 49】【动态规划-8】| Leetcode 121, 122, 123 需强化知识点 买卖股票系列 题目 121. 买卖股票的最佳时机 动态规划贪心:记录左侧的最小值 class Solution:def maxProfit(self, prices: List[int]) -> int:# n len(prices)# # 0…...
k8s metrics-server服务监控pod 的 cpu、内存
项目场景: 需要开启指标服务,依据pod 的 cpu、内存使用率进行自动的扩容或缩容 pod 的数量 解决方案: 下载 metrics-server 组件配置文件: wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/…...
电脑自带录屏在哪?电脑录屏,4个详细方法
在现代社会中,越来越多的人需要在电脑上录制视频,比如录制游戏操作、制作教学视频、演示文稿等等。因此,电脑录屏成为了一项非常重要的功能。那么电脑自带录屏在哪?本文将带领大家看看可以使用哪些方法进行录屏。 录屏方法一&…...
[Cloud Networking] Layer3 (Continue)
文章目录 1. DHCP Protocol1.1 DHCP 三种分配方式1.2 DHCP Relay (中继) 2. 路由协议 (Routing Protocol)2.1 RIP (Routing Information Protocol)2.2 OSPF Protocol2.2.1 OSPF Area2.2.2 Route ID / DR / BDR2.2.3 LSA / OSPF 邻居表 / LSDB / OSPF路由表 2.3 BGP Protocol2.4…...
missing authentication credentials for REST request
1、报错截图 2、解决办法 将elasticsearch的elasticsearch.yml的 xpack.security.enabled: true 改为 xpack.security.enabled: false...
Unity 从0开始编写一个技能编辑器_02_Buff系统的生命周期
工作也有一年了,对技能编辑器也有了一些自己的看法,从刚接触时的惊讶,到大量工作时觉得有一些设计的冗余,在到特殊需求的修改,运行效率低时的优化,技能编辑器在我眼中已经不再是神圣不可攀的存在的…...
计算机网络简答题
第一章 计算机网络 1.因特网是一个世界范围的计算机网络,记一个互联了遍及全世界的计算机设备的网络。 2.计算机网络将众多分散的、自治的(一台坏了不影响其他)计算机系统,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统。 3.计算机网络的组…...
探索Java 8 Stream API:现代数据处理的新纪元
Stream流 Stream初探:何方神圣? Stream流是一种处理集合数据的高效工具,它可以让你以声明性的方式处理数据集合。Stream不是存储数据的数据结构,而是对数据源(如集合、数组)的运算操作概念,支…...
vim 删除光标到最后一行的所有内容
在 Vim 中删除从光标所在位置到文件末尾的所有内容 删除从光标所在位置到文件末尾的所有内容使用 dG 命令 参考 删除从光标所在位置到文件末尾的所有内容 使用 dG 命令 确保你在正常模式下(按 Esc 键)。移动光标到你想要开始删除的位置。输入以下命令&…...
k8s之kubelet证书时间过期升级
1.查看当前证书时间 # kubeadm alpha certs renew kubelet Kubeadm experimental sub-commands kubeadm是一个用于引导Kubernetes集群的工具,它提供了许多命令和子命令来管理集群的一生周期。过去,某些功能被标记为实验性的,并通过kubeadm a…...
5G消息 x 文旅 | 一站式智慧文旅解决方案
5G消息 x 文旅 | 一站式智慧文旅解决方案 文旅 x 5G 消息将进一步强化资源整合,满足游客服务需求、企业营销需求、政府管理需求,推进文化旅游项目的智慧化、数字化,增强传播力、竞争力和可持续性。5G 消息的“原生入口”、“超强呈现”、“智…...
如何评估员工在新版FMEA培训后应用知识的效果?
随着制造业的快速发展,新版FMEA已成为企业提升产品质量、减少故障风险的关键一环。然而,培训只是第一步,如何有效评估员工在新版FMEA培训后应用知识的效果,才是确保培训成果转化的关键所在。 评估员工知识应用效果的首要步骤是制定…...
python脚本之解析命令参数
import requests import argparseprint(f"{__name__}:start")parser argparse.ArgumentParser(description使用方法) parser.add_argument(-p, --prefix, typestr, help域名) parser.add_argument(-t, --token, typestr, helptoken) parser.add_argument(-i, --queu…...
当JS遇上NLP:开启图片分析的奇幻之旅
前言 在当今科技飞速发展的时代,JavaScript(JS)作为广泛应用的编程语言,展现出了强大的活力与无限的可能性。与此同时,自然语言处理(NLP)领域也正在经历着深刻的变革与进步。 当这两者碰撞在一…...
trpc快速上手
tRPC (Type-safe Remote Procedure Call) 是一个用于构建类型安全的 API 的框架,它能够在前端和后端之间共享类型,确保类型安全性。这对于使用 TypeScript 的项目特别有用,因为它消除了前后端类型不一致的问题,提高了开发效率和代…...
知识图谱存在的挑战---隐私、安全和伦理相关和测试认证相关
文章目录 隐私、安全和伦理相关测试认证相关 隐私、安全和伦理相关 从部署拓扑结构而言,知识图谱技术以数据为核心、数据库为载体的方式来存储,有单机、云平台、集群及其组合的部署方式,结合大数据平台、云平台、业务系统、灾备、网络系统及其…...
课时155:脚本发布_简单脚本_命令罗列
2.1.1 命令罗列 学习目标 这一节,我们从 基础知识、简单实践、小结 三个方面来学习 基础知识 简介 目的:实现代码仓库主机上的操作命令功能即可简单实践 实践 查看脚本内容 #!/bin/bash # 功能:打包代码 # 版本: v0.1 # 作者: 书记 # …...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
