Spring Bean生命周期,好像人的一生。。

article/2025/8/24 7:58:29

大家好,我是老三,上节我们手撸了一个简单的IOC容器五分钟,手撸一个Spring容器!,这节我们来看一看Spring中Bean的生命周期,我发现,和人的一生真的很像。

简单说说IoC和Bean

IoC,控制反转,想必大家都知道,所谓的控制反转,就是把new对象的权利交给容器,所有的对象都被容器控制,这就叫所谓的控制反转。

控制反转

Bean,也不是什么新鲜玩意儿,它们就是一帮身不由己的Java对象,生命周期受到容器控制。

Bean生命周期和人生

Bean生命周期四大阶段

我们知道,bean的作用域有好几种,这篇文章只讨论完全被IoC容器控制的单例Bean。

对于普通的Java对象来说,它们的生命周期就是:

  • 实例化
  • 对象不再被使用时通过垃圾回收机制进行回收

这就像是生活在大自然里的动物,悄然出生,悄然死亡。

大象-图片来源网络

而对于Spring Bean的生命周期来说,可以分为四个阶段,其中初始化完成之后,就代表这个Bean可以使用了:

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

人和动物不一样,存在非常复杂的社会。

高楼大厦中的行人

我们来看看社会里的人,一生要经历哪些阶段,是不是和Bean的生命周期很像呢?

  • 出生:作为一个自然人降临在这个世界
  • 登记:登记身份证号,姓名,正式成为人类社会的一份子
  • 成长:接受教育,成为对社会有用的人
  • 工作:为社会创造价值
  • 死亡:人死如灯灭,不过人这盏灯灭了,还要把灯台埋起来

image-20220303101042089

Bean实例化的时机也分为两种,BeanFactory管理的Bean是在使用到Bean的时候才会实例化Bean,ApplicantContext管理的Bean在容器初始化的时候就回完成Bean实例化。

BeanFactory就是相对不那么健全的原始一些的社会,ApplicantContext是发达健全的现代社会。

BeanFactory和Applicantcontext

Bean详细生命周期

我们讲到了Bean容器四个阶段,会有一些容器级的方法,进行前置和后置的处理,比如InstantiationAwareBeanPostProcessor、BeanPostProcessor接口方法。这些方法独立于Bean之外,并且会注册到Spring容器中,在Spring容器创建Bean的时候,进行一些处理。

后处理器

这就好像,孩子出生之前,需要做一些准备,比如备孕、养胎、备产什么的,出生之后,需要做一些护理。孩子上学前后,也需要做一些学籍的管理。

那么有了各种各样的扩展之后,我们再接着看看Bean的详细的生命周期。首先,我们面临一个问题——Bean的生命周期从什么时候开始的呢?

上面写了,Bean实例化前后,可以进行一些处理,但是如果从Bean实例化前算开始,那么就要追溯到容器的初始化、beanDefiinition的加载开始。

所以这篇文章里,我们取生命周期直接从Bean实例化开始,但是大家也要知道,Bean实例化前后,可以使用后处理器进行处理,例如BeanFactoryPostProcessor、InstantiationAwareBeanPostProcessor。

大家也不要困扰,就像计算人生的起点,是从母亲怀孕算起,还是从孩子出生算起?我们这里取了出生开始而已。

Bean生命周期

  • 实例化:第 1 步,实例化一个 Bean 对象
  • 属性赋值:第 2 步,为 Bean 设置相关属性和依赖
  • 初始化:初始化的阶段的步骤比较多,5、6步是真正的初始化,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,初始化完成之后,Bean就可以被使用了
  • 销毁:第 8~10步,第8步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第9、10步真正销毁 Bean 时再执行相应的方法

我们发现Bean生命周期的详细过程,是不是也像人生的历程,出生、登记,不过是很短的事情。慢慢长大成人,要经历人生的四分之一,而成长,来源于教育,不管是学校的还是社会的,接受教育前,要登记学籍,上学的时候,自己还要努力……,到最后,要发一纸薄薄的文凭,标志着我们成为可以捶打的“社会人”。

然后,为社会奉献四十年。最后老去,离世。不过Bean的世界,没有退休——当然,也许,人的世界也没有退休。

人的曲线

我们发现中间的一些扩展过程也可以分四类:

Bean周期四类过程

  • 一:获取社会资源/Aware接口:Aware接口的作用是让Bean能拿到容器的一些资源,例如BeanNameAware可以拿到BeanName。就好像上学之前,要取一个学名——不知道多少人上学之前不知道自己大名叫什么,是吧?二毛。

  • 二:必备各种手续和证/后处理器:在Bean的生命周期里,会有一些后处理器,它们的作用就是进行一些前置和后置的处理,就像上学之前,需要登记学籍,上学之后,会拿到毕业证。

  • 三:个人选择/生命周期接口:人可能无法选择如何出生,但也许可以选择如何活着和如何死去,InitializingBean和DisposableBean 接口就是用来定义初始化方法和销毁方法的。

  • 四:主观能动/配置生命周期方法:环境影响人,人也在影响环境,成长的时候认真努力,衰亡的时候也可以豁达乐观。可以通过配置文件,自定义初始化和销毁方法。

PersonBean的一生

话不多说,接下来我们拿一个例子,来看看PersonBean的一生,我们先来看一下它的流程!

PersonBean的一生

用文字描述一下这个过程:

  1. Bean容器在配置文件中找到Person Bean的定义,这个可以说是妈妈怀上了。
  2. Bean容器使用Java 反射API创建Bean的实例,孩子出生了。
  3. Person声明了属性no、name,它们会被设置,相当于注册身份证号和姓名。如果属性本身是Bean,则将对其进行解析和设置。
  4. Person类实现了BeanNameAware接口,通过传递Bean的名称来调用setBeanName()方法,相当于起个学名。
  5. Person类实现了BeanFactoryAware接口,通过传递BeanFactory对象的实例来调用setBeanFactory()方法,就像是选了一个学校。
  6. PersonBean实现了BeanPostProcessor接口,在初始化之前调用用postProcessBeforeInitialization()方法,相当于入学报名。
  7. PersonBean类实现了InitializingBean接口,在设置了配置文件中定义的所有Bean属性后,调用afterPropertiesSet()方法,就像是入学登记。
  8. 配置文件中的Bean定义包含init-method属性,该属性的值将解析为Person类中的方法名称,初始化的时候会调用这个方法,成长不是走个流程,还需要自己不断努力。
  9. Bean Factory对象如果附加了Bean 后置处理器,就会调用postProcessAfterInitialization()方法,毕业了,总得拿个证。
  10. Person类实现了DisposableBean接口,则当Application不再需要Bean引用时,将调用destroy()方法,简单说,就是人挂了。
  11. 配置文件中的Person Bean定义包含destroy-method属性,所以会调用Person类中的相应方法定义,相当于选好地儿,埋了。

我们来看看代码!

PersonBean类

创建一个PersonBean,让它实现几个特殊的接口,我们来观察一下它的生命周期的流转。

public class PersonBean implements InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean {/*** 身份证号*/private Integer no;/*** 姓名*/private String name;public PersonBean() {System.out.println("1.调用构造方法:我出生了!");}public Integer getNo() {return no;}public void setNo(Integer no) {this.no = no;}public String getName() {return name;}public void setName(String name) {this.name = name;System.out.println("2.设置属性:我的名字叫"+name);}@Overridepublic void setBeanName(String s) {System.out.println("3.调用BeanNameAware#setBeanName方法:我要上学了,起了个学名");}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("4.调用BeanFactoryAware#setBeanFactory方法:选好学校了");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("6.InitializingBean#afterPropertiesSet方法:入学登记");}public void init() {System.out.println("7.自定义init方法:努力上学ing");}@Overridepublic void destroy() throws Exception {System.out.println("9.DisposableBean#destroy方法:平淡的一生落幕了");}public void destroyMethod() {System.out.println("10.自定义destroy方法:睡了,别想叫醒我");}public void work(){System.out.println("Bean使用中:工作,只有对社会没有用的人才放假。。");}}
  • 实现了InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean四个接口
  • 定义了no、name两个属性和对应的getter、setter方法
  • 定义了一个实例方法work

MyBeanPostProcessor

自定义了一个后处理器MyBeanPostProcessor:

public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("5.BeanPostProcessor.postProcessBeforeInitialization方法:到学校报名啦");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("8.BeanPostProcessor#postProcessAfterInitialization方法:终于毕业,拿到毕业证啦!");return bean;}
}

配置文件

定义一个配置文件spring-config.xml:

  • 使用setter注入
  • 定义init-method和destroy-method
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean name="myBeanPostProcessor" class="cn.fighter3.spring.life.MyBeanPostProcessor" /><bean name="personBean" class="cn.fighter3.spring.life.PersonBean"init-method="init" destroy-method="destroyMethod"><property name="idNo" value= "80669865"/><property name="name" value="张铁钢" /></bean></beans>

测试

最后测试一下,观察PersonBean的生命周期的流转:

public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");PersonBean personBean = (PersonBean) context.getBean("personBean");personBean.work();((ClassPathXmlApplicationContext) context).destroy();}
}

运行结果:

1.调用构造方法:我出生了!
2.设置属性:我的名字叫张铁钢
3.调用BeanNameAware#setBeanName方法:我要上学了,起了个学名
4.调用BeanFactoryAware#setBeanFactory方法:选好学校了
5.BeanPostProcessor#postProcessBeforeInitialization方法:到学校报名啦
6.InitializingBean#afterPropertiesSet方法:入学登记
7.自定义init方法:努力上学ing
8.BeanPostProcessor#postProcessAfterInitialization方法:终于毕业,拿到毕业证啦!
Bean使用中:工作,只有对社会没有用的人才放假。。
9.DisposableBean#destroy方法:平淡的一生落幕了
10.自定义destroy方法:睡了,别想叫醒我

看看,是不是和我们图中的流程一致。

这篇文章就不带大家跟进更多的源码了,如果大家对源码级别的Bean的生命周期感兴趣,可以看看AbstractApplicationContext类里的refresh方法,这个方法是AplicationContext容器初始化的关键点。在这个方法里,调用了finishBeanFactoryInitialization方法,这个方法里调用了getBean方法,getBean方法里调用了AbstractBeanFactorygetBean方法。

Bean生命周期源码追踪

最终经过一阵七拐八绕,到达了我们的目标——Bean创建的方法:doGetBean方法,在这个方法里可以看到Bean的实例化,赋值、初始化的过程,至于最终的销毁,可以看看ConfigurableApplicationContext#close()

结语

到这,这篇Bean的生命周期文章就走向destory了,自定义destory方法——回顾一下这篇文章的“一生”。

  • Bean的生命周期大致可以分为四个阶段:实例化、属性赋值、初始化、销毁,对应人生的出生、登记、成长、离世。
  • Bean生命周期中可以有很多扩展,就像人生的走向,会受很多影响,社会的环境、自身的选择、自己的努力。


参考:

[1]. 《Spring揭秘》

[2]. Spring官网

[3].《精通Spring4.X企业应用开发实战》

[4] .Spring Bean 生命周期 (实例结合源码彻底讲透)

[5].一文读懂 Spring Bean 的生命周期

[6].如何记忆 Spring Bean 的生命周期


干货文章首发,欢迎关注👇👇👇

http://chatgpt.dhexx.cn/article/0nh4RmVx.shtml

相关文章

Spring中bean的生命周期

Spring中的bean的生命周期主要包含四个阶段&#xff1a;实例化Bean --&#xff1e; Bean属性填充 --&#xff1e; 初始化Bean --&#xff1e;销毁Bean 首先是实例化Bean&#xff0c;当客户向容器请求一个尚未初始化的bean时&#xff0c;或初始化bean的时候需要注入另一个尚末初…

一文读懂 Spring Bean 的生命周期

欢迎大家关注我的微信公众号【老周聊架构】&#xff0c;Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。 一、前言 今天我们来说一说 Spring Bean 的生命周期&#xff0c;小伙伴们应该在面试中经常遇到&#xff0c;这是正常现象。…

ubuntu 换源深层次解析

换源也是一个容易出错的问题&#xff0c;本文以树莓派为例展开&#xff0c;x86也是一样的操作。 那么假设成立的话&#xff0c;就要记住我们是在树莓派&#xff08;arm&#xff09;上安装的ubuntu&#xff0c;不是X86&#xff0c;不是amd。 安装好系统后&#xff0c;我们第一…

[Linux]Ubuntu 换源 20.04 阿里源

注意&#xff0c;这篇文章其实不是简单的教你怎么换源&#xff0c;而是示例一种方法来换20.04的阿里源&#xff0c;其他源和版本大同小异。 笔者在写这篇文章的时候&#xff0c;20.04 还没有release出来正式版&#xff0c;但是已经可以在仓库里看到有源存在了&#xff0c;故写下…

Ubuntu换源详解,教你如何换源,并且解决常见的大坑

Ubuntu换源详解&#xff0c;教你如何换源&#xff0c;并且解决常见的大坑 记一次极不愉快的一次经历 首先注意&#xff0c;换源必须选择合适的版本&#xff0c;不可以在网上找一个下载源就直接去换 出现错误1&#xff1a; 由于没有公钥&#xff0c;无法验证下列签名 :NO_PUBK…

ubuntu 换源

网上应该可以找到很多关于ubuntu源的设置方法&#xff0c;但是如果不搞清楚就随便设置的话&#xff0c;不仅不能起到应有的效果&#xff0c;还会由于一些问题导致apt不可用。 最正确的更换源的方法应该如系统提示的&#xff1a; ## a.) add apt_preserve_sources_list: true …

20.04Ubuntu换源:提升软件下载速度和更新效率

在使用Ubuntu操作系统时&#xff0c;一个常见的优化措施是更改软件源&#xff0c;以提高软件下载速度和更新效率。软件源是指存储软件包的服务器&#xff0c;通过更换软件源&#xff0c;你可以选择更靠近你所在地区的服务器&#xff0c;从而加快软件下载速度&#xff0c;并减少…

Ubuntu快速更换源

Ubuntu更换源&#xff0c;零基础操作 提示&#xff1a;跟着一步步来&#xff0c;很快完成操作 为什么要换源&#xff1f;安装的linux系统默认的源是国外的&#xff0c;当用命令行安装软件&#xff08;比如安装gcc&#xff09;时下载速度非常慢&#xff0c;这里将源换成国内阿里…

Ubuntu系统换源

简单介绍一下源&#xff0c;源就是一个大仓库&#xff08;类似应用商店&#xff09;&#xff0c;系统下载软件需要从这个仓库下载&#xff0c;因为Ubuntu默认源是国外的&#xff0c;所以在下载东西的时候会出现下载速度很慢的情况&#xff0c;所以我们需要更换成国内的源&#…

ubuntu linux 换源,给Ubuntu换源

新手在使用Ubuntu的时候可能在升级时感觉很慢&#xff0c;如果这样他就需要换一个适合自己的源了。 下面我就简单的说一下怎样换源。 在终端里输入 sudo cp /etc/apt/sources.list /etc/apt/sources.list_backup (表示备份列表) 再输入 sudo gedit /etc/apt/sources.list 你就能…

ubuntu换源

ubuntu源 ubuntu换源方法1. 图形界面配置&#xff08;新手推荐&#xff09;2. 手动更改配置文件2.1 命令行替换2.2 手动替换 ubuntu的镜像源阿里源清华源中科大源 ubuntu换源方法 1. 图形界面配置&#xff08;新手推荐&#xff09; 依次打开&#xff1a;系统设置&#xff0c;…

Ubuntu换源的两种方法

Ubuntu系统自带的是国外的源,咱们国内用户使用的时候下载文件特别的慢,所以我们需要更换国内镜像源,这里我列举两个换源的方法,如果有新的方法可以在评论区补充。 首先第一步 我们需要更改root密码 sudo passwd root命令行: 首先备份源 也可以不备份 sudo cp /etc/apt/sou…

ubuntu更换源(清华源)并更新系统

首先更新自己需要的源 &#xff08;1&#xff09;打开清华园官网https://mirrors.tuna.tsinghua.edu.cn/&#xff1a; &#xff08;2&#xff09;搜索Ubuntu&#xff1a; &#xff08;3&#xff09;点击旁边的问号&#xff1a; &#xff08;4&#xff09;选择自己ubuntu的版…

Ubuntu更换国内源(apt更换源)

网上的教程大部分都是文本命令行的方式更换国内源的&#xff0c;其实Ubuntu18.04也提供了图形界面的方式&#xff0c;这里主要讲图形界面的方式&#xff0c;毕竟点点鼠标就能完成的事儿谁愿意去输命令啊&#xff0c;而且还容易出错&#xff0c;当然这里也附上命令行的方式。 可…

ubuntu换镜像源(ubuntu换源)

换源 Ubuntu中 大部分 的软件 安装/更新 都是利用 apt命令&#xff0c;从ubuntu的服务器 直接安装的 Ubuntu官方的服务器在国外&#xff0c;为了提高软件 安装/更新速度&#xff0c;ubuntu提供了 选择最佳服务器 的功能&#xff0c;可以帮助我们方便的找到一个速度最快的 镜像…

使用多线程往LIST添加数据 线程安全list

我们在日常写代码的过程中&#xff0c;经常会使用多线程提高效率&#xff0c;我们在使用多线程过程中难免会出现往List集合修改数据。 下面我们来尝试一下往ArrayList 添加数据&#xff1a; public static void main(String[] args) {List<Integer> list new ArrayList…

集合线程安全

集合线程安全 常用的集合类型如ArrayList&#xff0c;HashMap&#xff0c;HashSet等&#xff0c;在并发环境下修改操作都是线程不安全的&#xff0c;会抛出java.util.ConcurrentModificationException异常&#xff0c;这节主要记录如何在并发环境下安全地修改集合数据。 List…

线程安全的遍历list

遍历List的多种方式 在讲如何线程安全地遍历List之前&#xff0c;先看看通常我们遍历一个List会采用哪些方式。 方式一&#xff1a; for(int i 0; i < list.size(); i) {System.out.println(list.get(i)); } 方式二&#xff1a; Iterator iterator list.iterator(); while…

List的线程安全

List的线程安全 背景实验1. ArrayList2. synchronizedList3. 运行抛出异常ArrayIndexOutOfBoundsException异常原因 背景 Q&#xff1a;今天遇到一个场景&#xff0c;我们业务需要使用批量的数据进行操作&#xff0c;但是别人的接口只支持一个一个的查&#xff0c;所以需要用多…

线程安全 List 效率测试

List 常见类以及各自优缺点可自行参考 https://blog.csdn.net/weixin_39883065/article/details/111197724 本机环境 java 版本&#xff1a;1.8.0_161 window 信息&#xff1a; 测试代码 下面通过代码测试 List 线程安全类 Vector、Collections.synchronizedList(List lis…