1.前言概述
Spring Bean的生命周期是我们老生常谈的话题,不仅是高频面试题,也是我们对Spring的理解的入门。在Spring中,万物皆为Bean,统统由Spring容器进行管理,在需要的时候即可提供。我想我们都知道Spring中的两大亮点:
-
AOP 面向切面编程 -
IOC 控制反转
这篇文章主要围绕Bean对象的IOC来聊聊, 其实无论是IOC还是DI(依赖注入),这两种表达的都是一种概念,只是角度问题,就和我们生活中的坐北朝南一样,其实坐北也就是背靠着北实际上就是朝南。IOC控制反转,将对象的管理权交给了容器,是从容器角度触发;依赖注入是将容器中的对象注入到对应Bean对象,是从对象的角度出发的。基于此,我们来看看Spring是如何把对象加载完成放入容器,又是如何使用容器的中的对象注入到对应Bean中。
说到Bean,那必须得了解获取Bean的方式。获取Spring Bean的最顶层接口是BeanFactory, 后续又新增了ApplicationContext。后者继承了前者,说明后者提供了更强大的功能。事实上也确实如此,后者提供了国际化、事件机制、环境参数获取等,由于其强大的能力,因此占用的内存比BeanFactory多。打个比方,如果BeanFactory是一间茅草屋的话,ApplicationContext就是一幢别墅。Bean工厂可以为容器生产Bean,那生成Bean的原料一定是BeanDefinition,基于该原料可以生成对应的Bean。
2.单个Bean的生命周期
众所周知,任何一个对象都不能缺少实例化和属性填充,Spring Bean也不例外,Spring Bean还需要进行初始化的过程。Spring多出初始化过程是因为在Bean属性填充完毕后,需要提供很多对该bean后期的扩展埋点处理,自定义注解解析的过程就是一个典型的例子。那接下来就看看整个Bean的创建流程:

实例化前
实例化前后处理器可用于创建代理对象,对于普通bean而言的话,这里是返回null的, 然后执行doCreateBean方法执行真正的创建Bean的方法。

实例化
上述即可实现实例化Bean的流程,当然内部存在构造器推断的逻辑,也就是当Bean中存在多个构造器时,Spring得决定选择那一个构造器进行实例化对象。先检查是否有@Autowired注解的构造函数 ,如果没有检查高优先级的构造函数 ,最后使用无参的构造函数。

实例化后
调用postProcessMergedBeanDefinition缓存Bean中的注解的元数据,缓存注解的元数据是为后期服务其注解包括@Autowired,@Value,@PreDestory,@PostConstruct等注解。前两者是通过AutowiredAnnotationBeanPostProcessor实现的,后两者是通过CommonAnnotationBeanPostProcessor实现的。

addSingletonFactory方法是将后面的lambda表达式添加到第三级缓存,添加到第三级缓存只是在循环依赖开关开启的情况下执行,并且不论Bean是否发生循环依赖均会使用第三级缓存。

属性填充populateBean方法主要做了两件事:
-
执行实例化后的后处理器,如果该后处理器返回为false便不会执行后续逻辑(例如属性填充),该后处理器可以由开发者实现该接口中的方法; -
使用postProcessProperties方法给加上@Autowired和@Value填充Bean的属性;



初始化Bean
initializeBean方法主要做了四件事,我在上述图中进行了标注,接下来对这四步详细描述一下:
1. 执行实现Aware相关接口的方法
该接口下的实现有三个,分别是BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。这三者的作用我个人理解是Spring为了开发中扩展埋点所预留的,开发中很少使用。
2. 执行Bean初始化前的后处理器
该后处理器提供了postProcessBeforeInitialization方法用于扩展埋点,在初始化前执行的方法,就是在这个过程帮助我们执行了了PostConstruct注解的方法。
3. 执行Bean初始化方法
该过程分成两类:一类是执行实现InitializingBean接口的afterPropertiesSet方法;一类是自定义的初始化方法。如果两者都存在是都会执行的,前者先执行后者后执行,例子如下:
public class BeanD implements InitializingBean {
public BeanD(){
log.info("BeanD + Constructor");
}
//该方法是属于初始化第一类,如果想在SpringBoot启动时执行该方法,在该类上加一个成为Bean的注解就好了,例如:@Service
@Override
public void afterPropertiesSet() throws Exception {
log.info("BeanD + afterPropertiesSet");
}
//该方法属于第二类,如果想要执行时,在类上加注解是解决不了的
public void init() {
log.info("BeanD + init");
}
}
//第二类自定义初始化方法生效方式必须使用Bean注解的方式
@Configuration
@Slf4j
public class BeanConfiguration {
//在Bean注解的initMethod参数加上自定义的初始化方法,该方法名就是BeanD中自定义的初始化方法名
@Bean(initMethod = "init")
public BeanD beanD(){
return new BeanD();
}
}
4. 执行Bean初始化后的后处理器
对于执行初始化后的后处理器方法,我相信写过自定义注解的都不会陌生。通常情况下,需要对自定义注解的解析逻辑就是在这里完成。


创建完成后一定会调用afterSingletonCreation移除singletonsCurrentlyInCreation集合中的bean,表明这个bean已经创建完成了,不处于创建阶段。
将完整的Bean添加到第一级缓存并对第二三级缓存移除。
3.嵌入Bean的生命周期
嵌入Bean是指BeanA中注入了BeanB时,整个Bean的加载过程。Bean在加载过程不是同时去加载完成的,也就是Bean不会一批完成某个阶段,比如一批Bean进行初始化,一批bean进行实例化的。Bean加载是加载完成一个再去加载另一个,当BeanA中需要用到BeanB,这时会先放下BeanA加载去加载BeanB,等BeanB加载完成后再去完成BeanA剩下的加载步骤。整个流程如下:
上述的流程图是我在网上看到的,看着比较好引用过来的。
4.普通Bean循环依赖
三级缓存:
-
singletonObjects:第一级缓存,也是spring的单例池; -
earlySingletonObjects:第二级缓存,存放半成品Bean对象,也就是没有完成整个Bean加载过程的Bean对象。比如只填充一部分属性; -
singletonFactories:第三级缓存, 存放lambda的表达式,可用于生成代理对象
SpringBoot在2.6版本以后默认关闭了循环依赖,如果要开启循环依赖需要在配置文件中进行配置:spring.main.allow-circular-references=true
关闭循环依赖:Bean在加载的过程中只会添加到第一级缓存,剩下二级缓存没有利用到。当应用中出现循环依赖时会直接报错,分析一下循环依赖关闭情况下报错的原因:主要是这个集合singletonsCurrentlyInCreation
beanA在创建前会往singletonsCurrentlyInCreation集合中添加,表明beanA正在创建中,此时注入属性的beanB时发现beanB不存在,那需要去加载beanB,加载beanB的时候也会往上述的集合中添加beanB,在注入属性时,先在第一级缓存中没有找到(关闭情况下只利用了一级缓存,beanA没有加载完成,所以一级缓存肯定没有),然后又得去创建,在创建的过程中又得往singletonsCurrentlyInCreation集合中添加beanA的名字,这时由于beanA之前添加过一次所以会添加失败返回false,因此会抛出异常。
Note: Bean在创建前总是先去获取三级缓存中获取Bean,当不存在时才去创建。此处我理解当关闭循环依赖的时候不需要三级缓存中获取,只需要在第一级缓存中获取即可。

开启循环依赖解决问题最重要的是第三级缓存,因为当beanA在创建过程中会添加到第三级缓存中,beanB中需要注入beanA属性时,此刻即出现了循环依赖,这时在获取beanA时不会获取不到,因为开启循环依赖后会往第三级缓存中添加,取的时候也会去第三级缓存中取,对取出来的lambda表达式执行获得一个半成品的beanA注入beanB中,重而打破循环。
在发生循环依赖的情况下,最后一个打破循环的beanB可以完成创建,但是该beanB中的属性全部被填充并且beanB走完了整个Bean的加载过程,但是会存在属性beanA的去除beanB以外的其他属性为null的情况,这个beanA就是所谓的半成品。
5.总结
最后,为了方便大家理解,我给上述的整个过程整理成了一张流程图,熟悉整个Bean加载的流程。下图所示的是Bean的整个创建流程,紫色的表示是整个容器级别的,也就是会对除了自己以外的所有Bean都会执行。绿色表示的是Bean级别的,只会作用于当前Bean。
原文始发于微信公众号(处女座码农):Spring—普通Bean的生命周期
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251602.html