浅浅了解下Spring中生命周期函数(Spring6全攻略)

你好,这里是codetrend专栏“Spring6全攻略”。

Spring框架设计生命周期回调函数的主要目的是为了提供一种机制,使开发人员能够在对象创建、初始化和销毁等生命周期阶段执行特定的操作。这种机制可以帮助开发人员编写更加灵活和可维护的代码。

举个例子。

缓存预热是一种在程序启动或缓存失效之后,主动将热点数据加载到缓存中的策略。

通过缓存预热能避免第一次查询数据慢的问题。

那如何在应用启动的时候把数据全量写入缓存这呢?

这个时候就可以用到Spring的生命周期函数。

在服务创建的时候写一个init函数,加上注解@PostConstruct之后,就会在应用启动的时候调用。

这样一旦数据没有在缓存,就会二次写入。

整个过程用mermaid表示如下:

应用启动
调用init函数
数据在缓存中吗?
使用缓存数据
从数据库加载数据
写入缓存

生命周期函数有哪些使用场景

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的探索与实践,持续迭代中。

欢迎关注或者点个收藏~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/27192.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LogicFlow 学习笔记——4. LogicFlow 基础 边 Edge

边 Edge 和节点一样&#xff0c;LogicFlow 也内置一些基础的边。LogicFlow 的内置边包括&#xff1a; 直线 - line直角折现 - polyline贝塞尔曲线 - bezier 新建 src/views/Example/LogicFlow/Example08.vue 并编写如下代码&#xff1a; <script setup lang"ts&quo…

图解系列 图解Kafka之Consumer

文章目录 术语消息主题和分区集群和分区副本消费者组重新平衡组/分区再均衡消费者的分区策略群组协调者Coordinator 和群组领导者 Group Leader 流程初始化流程消费流程Consumer重平衡 消费者核心配置示例代码高级提交偏移量的几种方式自动提交手动同步提交手动异步提交提交特定…

鸿蒙开发电话服务:【@ohos.telephony.call (拨打电话)】

拨打电话 call模块提供呼叫管理功能&#xff0c;包括拨打电话、跳转到拨号界面、获取通话状态、格式化电话号码等。 如需订阅通话状态请使用[observer.on(callStateChange)] 说明&#xff1a; 本模块首批接口从API version 6开始支持。后续版本的新增接口&#xff0c;采用上角…

深入解析 Java 标准库:构建高效应用的基石

Java 标准库&#xff0c;也称为 Java API&#xff0c;是一组预先编写的类和接口&#xff0c;为 Java 应用程序提供了一系列丰富的功能。这些库被组织成包&#xff08;packages&#xff09;&#xff0c;每个包都包含了一组相关的类和接口&#xff0c;用于处理特定的任务&#xf…

前端数据库大盘点:解锁Web应用数据存储最优解!

在构建现代Web应用程序时&#xff0c;前端不仅要处理用户交互和展示逻辑&#xff0c;往往还需要承担一部分数据处理的任务&#xff0c;尤其是在单页面应用&#xff08;SPA&#xff09;和渐进式Web应用&#xff08;PWA&#xff09;中。这就引出了一个关键问题&#xff1a;前端应…

Python使用策略模式实现绘图功能

策略模式&#xff08;Strategy Pattern&#xff09;:允许定义一系列算法&#xff0c;将它们封装起来&#xff0c;使得它们可以互换。 实现绘制不同类型的图表&#xff08;如折线图、柱状图和饼图&#xff09;功能。 下面是一个示例&#xff0c;展示如何传入横坐标和纵坐标内容…

人工智能技术应用笔记(十二):搭建自带大模型微信,完美对接GPT-4o,Kimi等大模型,智能体平台Coze也能接

许多朋友对如何搭建自己的微信机器人非常感兴趣。今天就来教大家如何操作。 一、 准备工作 一台电脑或者云服务器&#xff0c;对配置要求不高&#xff0c;一般的电脑就行 大模型API调用的Key&#xff0c;比如GPT-4o&#xff0c;Kimi&#xff0c;Deepseek&#xff08;不知道怎…

使用Omnipeek进行Wifi/P2P抓包

前言 工作中解决Mirracast投屏连接失败的问题时&#xff0c;遇到了需要抓取wifi数据包的情况&#xff0c;记录一下配置和使用过程。 一、Omnipeek 的安装和配置 1.1 Omnipeek 安装 双击 setup.exe 进行安装 1.2 注册 注册时&#xff0c;版本写 71 &#xff0c;产生序列号和…

代码随想录算法训练营第36期 last day

最后一次更新&#xff0c;之后去复习专业课和简历 583两个字符串的删除操作 自己做出来了&#xff1a; Code: class Solution {public://找到公共子序列的最大长度dp 最小步数串1.size-dp串2.size-dp int minDistance(string word1, string word2) { vector<v…

Axios 请求响应拦截器 每次只发送一次请求封装

import axios from "axios"; import general from "/utils/general";const request axios.create({baseURL:general.serverUrl })const requestMap new Map() //保存每一次请求request.interceptors.request.use(req>{let oldRequest requestMap.get(re…

解决帝国cms栏目管理拼音乱码的问题

帝国CMS7.5版本utf-8版网站后台增加栏目生成乱码的问题怎么解决 1、需要改一个函数&#xff0c;并且增加一个处理文件&#xff0c;方法如下&#xff1a; 修改e/class/connect.php文件&#xff0c;找到ReturnPinyinFun函数&#xff0c;如未修改文件在4533-4547行&#xff0c;将…

sockjs-client和stompjs连接websocket服务被拒绝导致vue项目崩溃问题

1、前端连接服务器代码demo&#xff1a; // 引入stomp.js库 import SockJS from sockjs-client; import Stomp from stompjs;// WebSocket服务器地址 const ws new SockJS(http://localhost:8080/my-endpoint);// 初始化STOMP客户端 const stompClient Stomp.over(ws);// 连…

如何判断 NaN的方法?

判断数据类型的方法在JavaScript中有几种&#xff0c;主要包括&#xff1a; typeof 操作符&#xff1a;用于返回一个变量或表达式的数据类型的字符串表示。 typeof 42; // "number"typeof "Hello"; // "string"typeof true; // "bool…

node 中间件使用例子

NodeJS在中间件领域有着较为广泛的应用&#xff0c;他能做一些中间层事件&#xff0c;把服务端一部分的代码抽出来&#xff0c;减少处理冗余事情付出的代价&#xff0c;同时让服务真正做业务处理而不用关心页面的事情 常见的应用场景有&#xff1a; 跨域&#xff1a;解决跨域问…

二叉树左右树交换

leetcode 226题 翻转二叉树 题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3]…

背就有效!2024下《系统架构设计师》50个高频考点汇总

宝子们&#xff01;上半年软考已经结束一段时间了&#xff0c;准备备考下半年软考高级-系统架构设计师的小伙伴可以开始准备了&#xff0c;毕竟高级科目的难度可是不低的&#xff0c;相信参加过上半年架构的小伙伴深有体会。 这里给大家整理了50个高频考点&#xff0c;涵盖全书…

node更改npm缓存存储位置-并配置环境变量

更改缓存位置 node安装完成之后,在安装目录中新建一个存放缓存的文件夹node_cache 此时这个文件夹必须使用管理员权限才能更改,这使得命令行下使用npm进行下载的时候总是报权限不足的错误:permit 解决办法: 右键 -> 属性 -> 安全 -> 编辑 -> 选择user -> …

【Linux】进程_3

文章目录 五、进程3. 进程4. 进程状态 未完待续 五、进程 3. 进程 在当前&#xff0c;我们只能通过执行可执行程序来让操作系统帮我们启动进程&#xff0c;那我们如何使用代码来自己启动进程呢&#xff1f;我们可以使用 fork() 函数。作用是创建子进程。 我们创建一个程序来…

字节扣子搭建大模型擂台:匿名PK效果,用户当裁判,跑分时代要结束了

字节跳动的扣子&#xff08;coze.cn&#xff09;&#xff0c;给国产大模型们组了个大局—— 在同一个“擂台”上&#xff0c;两个大模型为一组&#xff0c;直接以匿名的方式PK效果&#xff01; 例如我们对两位参赛“选手”同时提问今年高考的题目&#xff1a; 阅读下面的材料&…

探索数字化转型:提升企业客户服务竞争力的策略

当前&#xff0c;数字经济已成为引领经济发展的“主引擎”。在这一背景下&#xff0c;客户服务领域也在发生着深刻变化&#xff0c;传统的以客服热线、人工客服为核心的客户服务模式已不能满足企业发展的需要&#xff0c;而数字化转型成为企业寻求突破的必然选择。 企业可利用大…