揭开Spring Bean生命周期的神秘面纱

        

目录

一、Spring IOC        

        1.1 Spring IOC 的加载过程

二、Spring Bean 生命周期

         2.1 实例化前置

        2.2 实例化后置

        2.3 属性赋值

        2.4 初始化前置

        2.5 初始化

        2.6 初始化后置

        2.7 Bean 销毁


        Spring 是一个开源的企业级Java应用程序框架,它简化了企业级应用程序开发,同时促进了松耦合、面向切面编程(AOP)、声明式事务管理以及基于Java EE平台的最佳实践。

        Spring 已经成为现代 Java 企业级应用开发的事实标准框架之一。今天就来了解下 Spring Bean的生命周期。

一、Spring IOC        

        Spring IOC 也叫控制反转或依赖注入,那谁控制谁呢?在以前,对象的创建销毁都是用户自己控制的,用了 IOC 之后对象的创建和销毁都交给了 Spring 容器来控制,用户不用管这些了,值管制业务需求就好。

        为什么叫控制反转呢?既然有反转,应该有正转。正转就是对象去找实例,比如你 new 了一个对象,那这个对象就指向了对象实例。反转就是通过实例来找对象。具体来说就是先通过容器找到实例,然后通过实例找到对象。

        1.1 Spring IOC 的加载过程

        整个加载过程大致如图所示:

        具体步骤如下:

        1、首先,通过 BeandefinitionReader 读取执行的配置文件生成 bean 的信息,然后到完整的 Beandefinition 定义信息,注意,这里存储的事 Bean 的定义信息,包括:是否单例、是否懒加载、是否抽象等等信息。就像工厂生产产品一样,准备好了原材料,下一步就该生成了。

        2、在生成完整的 Beandefinition 前,还可以通过 BeanFactoryPostProcessor 对 Beandefinition 进行增强,这个增强器可以设置多个。 

/*** 扩展方法--后置增强器(可修改bean的定义信息)*/
@Component
public class ExtBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {BeanDefinition service = beanFactory.getBeanDefinition("Service");System.out.println("扩展方法--可进行修改beanDefinition的定义信息");}
}

        3、得到完整的 Beandefinition 后就可以创建对象了, 创建对象的过程称为 bean 的生命周,也就是从实例化到销毁的过程。

二、Spring Bean 生命周期

        Spring Bean 的生命周期简单来说只有四个阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialiation)、销毁(Destruction),在这四个阶段中外加几个扩展点,就组成了完整的生命周期。

        主要逻辑在doCreateBean()方法中,逻辑很清晰,就是顺序调用一下三个方法。这三个方法与三个生命周期阶段一一对应,非常重要。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {// 实例化,通过反射实现instanceWrapper = createBeanInstance(beanName, mbd, args);}final Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// Allow post-processors to modify the merged bean definition.synchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Post-processing of merged bean definition failed", ex);}mbd.postProcessed = true;}}// Eagerly cache singletons to be able to resolve circular references// even when triggered by lifecycle interfaces like BeanFactoryAware.boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.Object exposedObject = bean;try {// 属性注入,包含循环依赖populateBean(beanName, mbd, instanceWrapper);// 初始化exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {throw (BeanCreationException) ex;}else {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);}}if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" +StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +"] in its raw version as part of a circular reference, but has eventually been " +"wrapped. This means that said other beans do not use the final version of the " +"bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);}catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);}return exposedObject;
}

        实例化前后的扩展通过 InstantiationAwareBeanPostprocessor 实现,初始化的前后扩展通过 BeanPostprocessor 实现,如下代码:

Component
public class InstantiationAwareBeanPostProcessorDemo implements InstantiationAwareBeanPostProcessor {// 实例化前置@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {System.out.println("postProcessBeforeInstantiation被调用了----在对象实例化之前调用-----beanName:" + beanName);// 默认什么都不做,返回nullreturn null;}// 实例化后置@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {System.out.println("postProcessAfterInstantiation被调用了---------beanName:" + beanName);//默认返回true,什么也不做,继续下一步return true;}// 属性修改@Overridepublic PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {System.out.println("postProcessPropertyValues被调用了---------beanName:"+beanName);// 此方法可对bean中的属性值进行、添加、修改、删除操作;// 对属性值进行修改,如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用,return pvs;}
}

         2.1 实例化前置

        实例化前置使用的是 postProcessBeforeInstantiation 方法,有两个参数,分别是beanClass 和 beanName,顾名思义,就是在对象实例化之前对 bean 对象的 class 信息进行修改或者扩展,以达到我们想要的功能,它的底层是动态代理 AOP 技术实现的;且是 bean 生命周期中最先执行的方法。

        返回值是 Object 类型,这意味着我们可以返回任何类型的值,由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成对象的目标对象的实例,也就是说,如果返回了非空的值,那么以后我们需要用到这个bean的时候,拿到的就现在返回的对象了,也就不会去走第二步去实例化对象了。

        然后就是执行对象实例化了,通过调用 doCreateBean 方法来实现。

        2.2 实例化后置

        实例化后置使用的是 postProcessAfterInstantiation 方法,这时实例已经创建,下一步就是属性赋值,相当于调用 set 方法,如果返回的是 false,意味着不会对该 bean 执行任何属性注入和其他初始化的逻辑。这种情况下,通常意味着开发者已经在该方法内部完成了所有必要的初始化工作,或者处于某种原因不让 Spring 容器继续初始化该 Bean。

        如果该方法返回的是 true,那会继续执行后续的流程。

        2.3 属性赋值

         属性赋值是通过 AbstractAutowireCapableBeanFactory.populateBean() 方法进行的。主要负责将 Bean 定义中的属性注入到已经实例化的 Bean 中。主要包含了 Setter 方法注入、构造器注入、字段注入等功能。

        2.4 初始化前置

        通过  BeanPostProcessor.postProcessBeforeInitialization() 来实现,在每一个 Bean 初始化之前执行,具体是指在调用初始化方法或 @PostConstruct 注解的方法执行前去执行前置操作。

        开发者可以根据业务需求,对特定的 Bean 在其初始化前做额外的增强功能,比如设置一些默认值、处理特殊的业务规则等。

        2.5 初始化

        Spring 初始化的方式有三种,分别是@PostConstruct、实现了 InitializingBean 接口、在注解 @Bean 中设置了 initMethod。

        通过 @PostConstruct 初始化实现如下:

import javax.annotation.PostConstruct;public class MyBean {@PostConstructpublic void init() {// 初始化逻辑}
}

        通过实现 InitializingBean 接口来初始化 Bean 时,需要重写 afterPropertiesSet 方法,Spring容器会在Bean实例化并设置好所有属性后调用该方法。

import org.springframework.beans.factory.InitializingBean;public class MyBean implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {// 初始化逻辑}
}

        Java配置中的 @Bean 注解: 使用 @Bean 注解定义Bean时,可以指定 initMethod 属性来设置初始化方法。

@Configuration
public class AppConfig {@Bean(initMethod = "init")public MyBean myBean() {return new MyBean();}
}// 对应的Java类
public class MyBean {public void init() {// 初始化逻辑}
}

        这些初始化方式的执行顺序大致为:

  1. @PostConstruct 注解的方法
  2. InitializingBean 接口的 afterPropertiesSet 方法
  3. init-method 指定的方法

        2.6 初始化后置

        通过调用 postProcessAfterInitialization 来实现初始化后置扩展,会在 Spring 容器完成对 Bean 的初始化之后,但在 Bean 交付给 Spring 容器进行使用之前执行。比如

  • 对 Bean 的属性值进行最后的修改或补充设置
  • 对 Bean 进行迭代增强,比如动态代理等
  • 甚至是完全替换掉这个 Bean 实例

        2.7 Bean 销毁

        当Spring容器不再需要一个Bean或容器本身正在关闭时,会按照一定的顺序对Bean进行销毁。销毁的方式如下:

  • 如果一个Bean实现了org.springframework.beans.factory.DisposableBean接口,那么在销毁阶段,Spring会调用其destroy()方法。
  • 若 Bean 中存在一个方法使用了@PreDestroy注解,那么在销毁 Bean 之前,Spring 会调用这个方法。
  • 如果在 @Bean 注解中使用了 destroy-method属性,销毁 Bean 时会调用该方法

        总之,Spring Bean 的生命周期在保持基本的功能基础上,引入了诸多扩展点,为开发者提供了诸多便捷的功能,以实现更加灵活的定制化需求。

        

往期经典推荐:

Redis分布式锁:保障微服务架构下的并发控制与数据一致性实战指南-CSDN博客

探析Drools规则引擎的工作原理_规则引擎执行性能-CSDN博客

实时数据传输的新里程——Server-Sent Events(SSE)消息推送技术-CSDN博客

Spring循环依赖的成因与破局-CSDN博客

SpringBoot项目并发处理大揭秘,你知道它到底能应对多少请求洪峰?_一个springboot能支持多少并发-CSDN博客

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

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

相关文章

开源数据湖iceberg, hudi ,delta lake, paimon对比分析

Iceberg, Hudi, Delta Lake和Paimon都是用于大数据湖(Data Lake)或数据仓库(Data Warehouse)中数据管理和处理的工具或框架,但它们在设计、功能和适用场景上有所不同。 Iceberg: Iceberg是用于大型分析表的高性能格式。Iceberg将SQL表的可靠性和简易性带入到大数据领域,同…

【java的本地锁到分布式锁介绍】

文章目录 1.java本地自带锁介绍及应用synchronized&#xff08;1&#xff09;synchronized原理和优化&#xff08;2&#xff09;synchronized作用&#xff08;3&#xff09;synchronized的使用 CAS(1) CAS原理&#xff08;2&#xff09;CAS和synchronized优缺点 lock 2.分布式锁…

idea常用配置

IDEA设置全局配置 参考&#xff1a;IDEA设置全局配置_idea如何打开一个项目,全局设置-CSDN博客 idea提交代码到git或svn上时&#xff0c;怎么忽略.class、.iml文件和文件夹等不必要的文件 参考&#xff1a;idea提交代码到git或svn上时&#xff0c;怎么忽略.class、.iml文件和文…

LeetCode-74. 搜索二维矩阵【数组 二分查找 矩阵】

LeetCode-74. 搜索二维矩阵【数组 二分查找 矩阵】 题目描述&#xff1a;解题思路一&#xff1a;先二分查找行&#xff0c;再二分查找列。解题思路二&#xff1a;暴力遍历&#xff0c;也能过。解题思路三&#xff1a;用python的in。 题目描述&#xff1a; 给你一个满足下述两条…

VSCODE目录树缩进调整

VSCode默认的缩进太小了&#xff0c;简直看不出来&#xff0c;很容易弄混目录。在设置里修改就行了。 修改后效果&#xff1a;

何为网络协议?一图知晓网络过程。

网络协议就是计算机之间沟通的语言 为了有效地交流&#xff0c;计算机之间需要一种共同的规则或协议&#xff0c; 就像我们和老外沟通之前&#xff0c;要先商量好用哪种语言&#xff0c; 要么大家都说中文&#xff0c;要么大家都说英语&#xff0c;这才能有效地沟通。 网络协…

OSPF中配置静态路由负载分担实验简述

OSPF中配置静态路由负载分担 实验简述 在静态路由负载分担中&#xff0c;多个路由器被配置为共享负载的目标&#xff0c;以实现流量的均衡分配。 到达目的地有N条相同度量值的路径&#xff0c;默认值60&#xff0c;N条路由是等价路由&#xff0c;数据报文在N条链路上轮流发送。…

探索基于WebRTC的有感录屏技术开发流程

title: 探索基于WebRTC的有感录屏技术开发流程 date: 2024/4/7 18:21:56 updated: 2024/4/7 18:21:56 tags: WebRTC录屏技术屏幕捕获有感录屏MediaStream实时传输音频录制 第一章&#xff1a;技术原理 WebRTC&#xff08;Web Real-Time Communication&#xff09;是一种开放源…

SQL Sever 2008 安装教程

先从官网下载程序&#xff1a;下载地址 打开上述链接后&#xff0c;点击下载按钮。 就会跳出下面这个界面&#xff0c;如果你的电脑是64位的请选择下图中这两个程序。 下载完成后&#xff0c;在电脑磁盘中找到这两个文件&#xff0c;注意安装的顺序&#xff0c;先安装 SQLEXPR…

Linux:软硬链接及动静态库

一、Linux中的链接文件 1.1硬链接及应用场景 ln//创建硬链接 我们再创建一个硬链接生成的文件。 我们可以看到mlink.hard的inode和makefile.c的inode都是一样的&#xff0c;inode一样里面的数据自然也是一样。相当于对make.file进行了一个重命名&#xff0c;所以硬链接一定没…

2023年蓝桥杯省赛——买二赠一

目录 题目链接&#xff1a;1.买二赠一 - 蓝桥云课 (lanqiao.cn) 题目描述 输入格式 输出格式 样例输入 样例输出 样例说明 思路 队列贪心 代码实现 总结 题目链接&#xff1a;1.买二赠一 - 蓝桥云课 (lanqiao.cn) 题目描述 某商场有 N 件商品&#xff0c;其中第 i 件…

漫谈:“标准”是一种幻觉 C++语言标准的意义

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 “标准”这个词很迷惑&#xf…

铸铁平台的单围和双围是什么——北重机械

铸铁平台的单围和双围是指平台的围栏结构。单围指平台只有一面围栏&#xff0c;通常用于平台的三个边界上&#xff0c;另一边是与建筑物相连的。双围指平台两侧围栏都有&#xff0c;即平台四个边界都有围栏。双围结构比单围结构更加安全&#xff0c;可以防止人员和物品从平台四…

CleanmyMac 苹果电脑清理软件,为你的 Mac 提速!

Apple Macbook 已成为当今职场不可或缺的高效助手&#xff0c;尤其在普遍的远程办公场景下&#xff0c;其运行流畅度对工作效率及用户体验至关重要。虽然长期使用会使Mac电脑性能自然衰退&#xff0c;但大部分导致系统变慢的因素其实可经由用户自行调整得到显著改善&#xff0c…

linux 设置命令输入行高亮(与软件无关:xshell等)

在命令执行后输出内容比较多的情况下,很难查看自己的历史命令 这个配置是系统的配置:取消.bashrc文件中force_color_prompt=yes的注释即可 (和连接服务器的软件无关) 具体的操作如下: 执行以下命令,查看配置所在的行数root@hecs-166280:~# cat .bashrc -n | grep force_…

Java Lambda 表达式(详细)

Java Lambda 表达式 Lambda 的发展史 Java Lambda 表达式是在 Java 8 版本中引入的重要特性&#xff0c;它描述了一种更简洁、更灵活的方式来处理函数式编程。 在 Java 8 之前&#xff0c;要实现函数式编程&#xff0c;需要通过匿名类实现接口的方式。这样的代码通常比较冗长…

深入MyBatis的动态SQL:概念、特性与实例解析

MyBatis 是一个优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。 MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。它可以使用简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO&#xff0c;即普通的 Java 对象为数据库中的记…

MacOS Docker 部署 Redis 数据库

一、简介 Redis是一个开源的、使用C语言编写的、基于内存亦可持久化的Key-Value数据库&#xff0c;它提供了多种语言的API&#xff0c;并支持网络交互。Redis的数据存储在内存中&#xff0c;因此其读写速度非常快&#xff0c;每秒可以处理超过10万次读写操作&#xff0c;是已知…

milvus search api的数据结构

search api的数据结构 此api的功能是向量相似度搜索(vector similarity search) 一个完整的search例子: 服务端collection是一个hnsw类型的索引。 import random from pymilvus import (connections,Collection, )dim 128if __name__ __main__:connections.connect(alias…

Django检测到会话cookie中缺少HttpOnly属性手工复现

一、漏洞复现 会话cookie中缺少HttpOnly属性会导致攻击者可以通过程序(JS脚本等)获取到用户的cookie信息&#xff0c;造成用户cookie信息泄露&#xff0c;增加攻击者的跨站脚本攻击威胁。 第一步&#xff1a;复制URL&#xff1a;http://192.168.43.219在浏览器打开&#xff0c;…