去字节面试,直接让人出门左拐:Bean 生命周期都不知道!

article/2025/8/24 7:50:00

Spring Bean 的生命周期,面试时非常容易问,这不,前段时间就有个粉丝去字节面试,因为不会回答这个问题,一面都没有过。

如果只讲基础知识,感觉和网上大多数文章没有区别,但是我又想写得稍微深入一点。

考虑很多同学不喜欢看源码,我就把文章分为 2 大部分,前面是基础知识,主要方便大家面试和学习,后面是源码部分,对源码感兴趣的同学可以继续往后面看。

1. 基础知识

1.1 什么是 IoC ?

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

IoC 很好地体现了面向对象设计法则之一 —— 好莱坞法则:“别找我们,我们找你”,即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

理解好 IoC 的关键是要明确 “谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”。

谁控制谁,控制什么?

传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象。而 IoC 是由专门一个容器来创建这些对象,即由 IoC 容器来控制对象的创建。

  • 谁控制谁?当然是 IoC 容器控制了对象;
  • 控制什么?主要控制了外部资源获取(不只是对象,比如包括文件等)。

为何是反转,哪些方面反转了?

有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转,而反转则是由容器来帮忙创建及注入依赖对象。

  • 为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
  • 哪些方面反转了?依赖对象的获取被反转了。

1.2 Bean 生命周期

对 Prototype Bean 来说,当用户 getBean 获得 Prototype Bean 的实例后,IOC 容器就不再对当前实例进行管理,而是把管理权交由用户,此后再 getBean 生成的是新的实例。

所以我们描述 Bean 的生命周期,都是指的 Singleton Bean。

Bean 生命周期过程:

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

整个执行流程稍微有些抽象,下面我们通过代码,来演示执行流程。

1.3 执行流程

创建一个 LouzaiBean。

public class LouzaiBean implements InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean {/*** 姓名*/private String name;public LouzaiBean() {System.out.println("1.调用构造方法:我出生了!");}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使用中:工作,只有对社会没有用的人才放假。。");}
}

自定义一个后处理器 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;}
}

applicationContext.xml 配置文件(部分)。

<bean name="myBeanPostProcessor" class="demo.MyBeanPostProcessor" />
<bean name="louzaiBean" class="demo.LouzaiBean"init-method="init" destroy-method="destroyMethod"><property name="name" value="楼仔" />
</bean>

测试入口:

public class MyTest {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("classpath:applicationContext.xml");LouzaiBean louzaiBean = (LouzaiBean) context.getBean("louzaiBean");louzaiBean.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 生命周期流程图能完全对应起来。

1.4 扩展方法

我们发现,整个生命周期有很多扩展过程,大致可以分为 4 类:

  • Aware 接口:让 Bean 能拿到容器的一些资源,例如 BeanNameAware 的setBeanName(),BeanFactoryAware 的setBeanFactory();
  • 后处理器:进行一些前置和后置的处理,例如 BeanPostProcessor 的postProcessBeforeInitialization()和postProcessAfterInitialization();
  • 生命周期接口:定义初始化方法和销毁方法的,例如 InitializingBean 的afterPropertiesSet(),以及 DisposableBean 的destroy();
  • 配置生命周期方法:可以通过配置文件,自定义初始化和销毁方法,例如配置文件配置的init()和destroyMethod()。

2. 源码解读

注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不一样!!!

上面的知识,网上其实都有,下面才是我们的重头戏,让你跟着我走一遍代码流程。

2.1 代码入口

这里需要多跑几次,把前面的 beanName 跳过去,只看 louzaiBean。

进入 doGetBean(),从 getSingleton() 没有找到对象,进入创建 Bean 的逻辑。

2.2 实例化

进入 doCreateBean() 后,调用 createBeanInstance()。

进入 createBeanInstance() 后,调用 instantiateBean()。

走进示例 LouzaiBean 的方法,实例化 LouzaiBean。

2.3 属性赋值

再回到 doCreateBean(),继续往后走,进入 populateBean()。

这个方法非常重要,里面其实就是依赖注入的逻辑,不过这个不是我们今天的重点,大家如果对依赖注入和循环依赖感兴趣,可以翻阅我之前的文章。

进入 populateBean() 后,执行 applyPropertyValues()

进入 applyPropertyValues(),执行 bw.setPropertyValues()

进入 processLocalProperty(),执行 ph.setValue()。

走进示例 LouzaiBean 的方法,给 LouzaiBean 赋值 name。

到这里,populateBean() 就执行完毕,下面开始初始化 Bean。

2.4 初始化

我们继续回到 doCreateBean(),往后执行 initializeBean()。

走进示例 LouzaiBean 的方法,给 LouzaiBean 设置 BeanName。

回到 invokeAwareMethods()。

走进示例 LouzaiBean 的方法,给 LouzaiBean 设置 BeanFactory。

第一次回到 initializeBean(),执行下面逻辑。

这里需要多循环几次,找到 MyBeanPostProcessor 的策略方法。

我们自己定义的后置处理方法。

第二次回到 initializeBean(),执行下面逻辑。

走进示例 LouzaiBean 的方法,执行 afterPropertiesSet()。

返回 invokeInitMethods(),执行下面逻辑。

进入 invokeCustomInitMethod(),执行下面逻辑。

走进示例 LouzaiBean 的方法,执行 init()。

第三次回到 initializeBean(),执行下面逻辑。

我们自己定义的后置处理方法。

到这里,初始化的流程全部结束,都是围绕 initializeBean() 展开。

2.4 销毁

当 louzaiBean 生成后,后面开始执行销毁操作,整个流程就比较简单。

走进示例 LouzaiBean 的方法,执行 destroy()。

回到 destroy(),执行下面逻辑。

走进示例 LouzaiBean 的方法,执行 destroyMethod()。

到这里,所有的流程全部结束,文章详细描述所有的代码逻辑流转,你可以完全根据上面的逻辑,自己 debug 一遍。

3. 写在最后

我们再回顾一下几个重要的方法:

  • doCreateBean():这个是入口;
  • createBeanInstance():用来初始化 Bean,里面会调用对象的构造方法;
  • populateBean():属性对象的依赖注入,以及成员变量初始化;
  • initializeBean():里面有 4 个方法,

先执行 aware 的 BeanNameAware、BeanFactoryAware 接口;

再执行 BeanPostProcessor 前置接口;

然后执行 InitializingBean 接口,以及配置的 init();

最后执行 BeanPostProcessor 的后置接口。

destory():先执行 DisposableBean 接口,再执行配置的 destroyMethod()。

对于 populateBean(),里面的核心其实是对象的依赖注入,这里也是常考的知识点,比如循环依赖,大家如果对这块也感兴趣,可以私下和我交流。


http://chatgpt.dhexx.cn/article/8hUhohDM.shtml

相关文章

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

大家好&#xff0c;我是老三&#xff0c;上节我们手撸了一个简单的IOC容器五分钟&#xff0c;手撸一个Spring容器&#xff01;&#xff0c;这节我们来看一看Spring中Bean的生命周期&#xff0c;我发现&#xff0c;和人的一生真的很像。 简单说说IoC和Bean IoC&#xff0c;控制…

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;所以需要用多…