浅浅了解下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#createBean
AbstractAutowireCapableBeanFactory#doCreateBeanAbstractAutowireCapableBeanFactory#doCreateBean
AbstractAutowireCapableBeanFactory#initializeBean
AbstractAutowireCapableBeanFactory#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 # 作者: 书记 # …...

借助ollama实现AI绘画提示词自由,操作简单只需一个节点!
只需要将ollama部署到本地,借助comfyui ollama节点即可给你的Ai绘画提示词插上想象的翅膀。具体看详细步骤! 第一步打开ollama官网:https://ollama.com/,并选择models显存太小选择的是llama3\8b参数的instruct-q6_k的这个模型。 运…...

PyTorch -- Visdom 快速实践
安装:pip install visdom 注:如果安装后启动报错可能是 visdom 版本选择问题 启动:python -m visdom.server 之后打开出现的链接 http://localhost:8097Checking for scripts. Its Alive! INFO:root:Application Started INFO:root:Working…...

基于xilinx FPGA的QSFP调试使用经验
1 概述 本文用于记录QSFP在调试使用时遇到的一些经验教训,防止后来者踩相同的坑。 参考手册: 《AMQ28-SR4-M1_V1.0》 《QSFP-DD-Hardware-rev4p0-9-12-18-clean》 2 QSFP简介 QSFP(Quad Small Form-facor Pluggable)即四通道SFP…...

WPF 使用Image控件显示图片
Source属性 Source属性用来告诉Image组件要展示哪张图片资源的一个入口,通常是图片的路径。也许是本地路径,也许是网络路径。 本地图片路径加载方式 使用相对路径,相对于工程目录的路径,当设置Width属性时,图片会等…...

合肥工业大学内容安全实验一:爬虫|爬新闻文本
✅作者简介:CSDN内容合伙人、信息安全专业在校大学生🏆 🔥系列专栏 :合肥工业大学实验课设 📃新人博主 :欢迎点赞收藏关注,会回访! 💬舞台再大,你不上台,永远是个观众。平台再好,你不参与,永远是局外人。能力再大,你不行动,只能看别人成功!没有人会关心你付…...

自动驾驶---Perception之视觉点云雷达点云
1 前言 在自动驾驶领域,点云技术的发展历程可以追溯到自动驾驶技术的早期阶段,特别是在环境感知和地图构建方面。 在自动驾驶技术的早期技术研究中,视觉点云和和雷达点云都有出现。20世纪60年代,美国MIT的Roberts从2D图像中提取3D…...

maven 显式依赖包包含隐式依赖包,引起依赖包冲突
问题:FlinkCDC 3.0.1 代码 maven依赖包冲突 什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的。每个显式声明的类包都会依赖于一些其它…...

Spring应用如何打印access日志和out日志(用于分析请求总共在服务耗费多长时间)
我们经常会被问到这样一个问题。你接口返回的好慢呀,能不能提升一下接口响应时间啊?这个时候我们就需要去分析,为什么慢,慢在哪。而这首先应该做的就是确定接口返回时间过长确实是在服务内消耗的时间。而不是我们将请求发给网关或…...

SpringBoot整合SpringDataRedis
目录 1.导入Maven坐标 2.配置相关的数据源 3.编写配置类 4.通过RedisTemplate对象操作Redis SpringBoot整合Redis有很多种,这里使用的是Spring Data Redis。接下来就springboot整合springDataRedis步骤做一个详细介绍。 1.导入Maven坐标 首先,需要导…...

电脑怎么录制游戏视频?轻松捕捉每一帧精彩
随着游戏产业的蓬勃发展,越来越多的玩家不仅满足于在游戏世界中的探索与冒险,更希望将自己的游戏精彩瞬间记录下来,分享给更多的朋友。可是电脑怎么录制游戏视频呢?本文旨在为广大游戏爱好者提供一份详细的电脑游戏视频录制攻略&a…...