带着问题去分析:Spring Bean 生命周期
1: Bean在Spring容器中是如何存储和定义的
Bean在Spring中的定义是_org.springframework.beans.factory.config.BeanDefinition_接口,BeanDefinition里面存储的就是我们编写的Java类在Spring中的元数据,包括了以下主要的元数据信息:
1:Scope(Bean类型):包括了单例Bean(Singleton)和多实例Bean(Prototype)
2:BeanClass: Bean的Class类型
3:LazyInit:Bean是否需要延迟加载
4:AutowireMode:自动注入类型
5:DependsOn:Bean所依赖的其他Bean的名称,Spring会先初始化依赖的Bean
6:PropertyValues:Bean的成员变量属性值
7:InitMethodName:Bean的初始化方法名称
8:DestroyMethodName:Bean的销毁方法名称
同时BeanDefinition是存储到_org.springframework.beans.factory.support.DefaultListableBeanFactory类中维护的BeanDefinitionMap_中的,源码如下:
Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
了解了BeanDefinition的基础信息和存储位置后,接下来看看创建好的Bean实例是存储在什么地方的,创建好的Bean是存储在:_org.springframework.beans.factory.support.DefaultSingletonBeanRegistry_类中的
_singletonObjects_中的,Key是Bean的名称,Value就是创建好的Bean实例:
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
了解了基本信息之后,就可以带着下面两个关键问题去分析Spring Bean的生命周期了:
1:Java类是如何被 Spring 扫描从而变成 BeanDefinition 的?
2:BeanDefinition是如何被 Spring 加工创建成我们可以直接使用的 Bean实例的?
2:Java类是如何被Spring扫描成为BeanDefinition的?
在Spring中定义Bean的方式有非常多,例如使用XML文件、注解,包括:@Component,@Service,@Configuration等,下面就以@Component注解为例来探究Spring是如何扫描我们的Bean的。我们知道使用@Component注解来标记Bean是需要配合@ComponentScan注解来使用的,而我们的主启动类上标注的@SpringBootApplication注解中就默认继承了@ComponentScan注解
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication
所以最初的问题就转化成了@ComponentScan注解是如何在Spring中运作的
2.1 @ComponentScan注解是如何运作的
在Spring框架中,这个注解对应的处理类是_ComponentScanAnnotationParser,这个类的parse_方法是主要的处理逻辑,这个方法简要处理逻辑如下:
1:获取@ComponentScan注解中的basePackage属性,若没有则默认为该注解所标注类所在的包路径
2:使用ClassPathBeanDefinitionScanner的scanCandidateComponents方法扫描classpath:+basePackage+/*.class**下的所有类资源文件
3:最后循环判断扫描的所有类资源文件,判断是否包含@Component注解,若有则将这些类注册到beandefinitionMap中
自此,我们代码里写的Java类,就被Spring扫描成BeanDefinition存储到了BeanDefinitionMap中了,扫描的细节大家可以去看看这个类的源码
3:Spring如何创建我们的Bean实例的
Spring把我们编写的Java类扫描成BeanDefinition之后,就会开始创建我们的Bean实例了,Spring将创建Bean的方法交给了_org.springframework.beans.factory.support.AbstractBeanFactory#getBean_方法,接下来就来看看getBean方法是如何创建Bean的
getBean方法的调用逻辑如下:getBean--> doGetBean --> createBean --> doCreateBean,最终Spring会使用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法来创建Bean,创建Bean实例的主要逻辑分为了四个部分:创建Bean实例,填充Bean属性,初始化Bean,销毁Bean,接下来我们分别对这个四个部分进行探究
3.1 创建Bean实例
创建Bean实例的方法入口如下:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#__createBeanInstance
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
这个方法的主要逻辑是:推断出创建该Bean的构造器方法和参数,然后使用Java反射去创建Bean实例
3.2 填充Bean属性值populateBean方法
在这个方法中,主要是解析Bean需要注入的成员属性,然后将这些属性注入到该Bean中,如果该Bean有依赖的其他Bean则会优先去创建依赖的Bean,然后返回来继续创建该Bean,注意这里就会产生Bean创建的循环依赖问题,在本文的第6节中会详细说明
3.4:初始化Bean(initializeBean方法)
初始化Bean主要包括了四个部分:
3.4.1:invokeAwareMethods
在这个方法中主要调用实现的Aware接口中的方法,包括了BeanNameAware.setBeanName,BeanClassLoaderAware.setBeanClassLoader,BeanFactoryAware.setBeanFactory,这三个方法

Aware接口的功能:通过调用Aware接口中的set方法,将Spring容器中对应的Bean注入到正在创建的Bean中
3.4.2:调用前置处理方法:applyBeanPostProcessorsBeforeInitialization
在这个方法中主要是获取Spring容器中所有实现了_org.springframework.beans.factory.config.BeanPostProcessor接口的的实现类,然后循环调用postProcessBeforeInitialization_方法来加工正在创建的Bean

所以在这个方法中我们可以自定义_BeanPostProcessor_来扩展Bean的功能,实现自己的加工逻辑
3.4.3:调用Bean相关的初始化方法:
3.4.3.1 如果是InitializingBean则调用afterPropertiesSet方法

在这个流程中,Spring框架会判断正在创建的Bean是否实现了InitializingBean接口,如果实现了就会调用_afterPropertiesSet_方法来执行代码逻辑。
3.4.3.2 调用自定义初始化方法:initMethod
在这个流程中主要调用我们自定义的初始化方法,例如在xml文件中配置的_init-method和destory-method或者使用注解配置的@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")_ 方法

3.4.3.3:调用后置处理方法:applyBeanPostProcessorsAfterInitialization
在这个方法中主要是获取Spring容器中所有实现了_org.springframework.beans.factory.config.BeanPostProcessor接口的的实现类,然后循环调用postProcessAfterInitialization_来加工正在创建的Bean

在这个方法中我们可以自定义_BeanPostProcessor_来扩展Bean的功能,实现自己的加工逻辑
4:注册Bean销毁方法
在这里主要是注册Bean销毁时Spring回掉的方法例如:
1:xml文件中配置的destroy-method方法或者_@Bean注解中配置的destroyMethod_方法
2:_org.springframework.beans.factory.DisposableBean接口中的destory_方法
5:总结
到这里,从我们编写的Java类到Spring容器中可使用的Bean实例的创建过程就完整的梳理完成了,了解Bean的创建过程能够使我们更加熟悉Bean的使用方法,同时我们也可以在创建Bean的过程中新增自己的处理逻辑,从而实现将自己的组件接入Spring框架
6:Spring循环依赖的解决方法
Spring在创建Bean实例的时候,有时避免不了我们编写的Java类存在互相依赖的情况,如果Spring对这种互相依赖的情况不做处理,那么就会产生创建Bean实例的死循环问题,所以Spring对于这种情况必须特殊处理,下面就来探究Spring是如何巧妙处理Bean之间的循环依赖问题
6.1 暴露钩子方法getEarlyBeanReference
首先对于单实例类型的Bean来说,Spring在创建Bean的时候,会提前暴露一个钩子方法来获取这个正在创建中的Bean的地址引用,其代码如下:


如上面的代码所示,此时会在_singletonFactories这个Map中提前储存这个钩子方法singletonFactory_,从而能够提前对外暴露这个Bean的地址引用,那么为什么获取地址引用需要包装成复杂的方法呢?下面会解释
6.2 其他Bean获取提前暴露的Bean的地址引用
当其他Bean需要依赖正在创建中的Bean的时候,就会调用getSingleton方法来获取需要的Bean的地址引用

如上诉代码所示,在获取Bean的时候会从三个地方来获取
1:singletonObjects :这个是存放已经完全创建完成的Bean实例的Map
2:earlySingletonObjects :这个是存放用提前暴露的钩子方法创建好的Bean实例的Map
3:singletonFactories :这个是用来存放钩子方法的Map
当获取依赖的Bean的时候,就会调用钩子方法getEarlyBeanReference来获取提前暴露的Bean的引用,这个方法的源码如下:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
如上面的源码所示,这个方法主要是需要调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法来提前处理一下尚未创建完成的Bean,而getEarlyBeanReference方法有逻辑的实现类只有一个**org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator,**这个类就是创建Aop代理的类,其代码如下:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
//提前标记这个bean已经创建过代理对象了
this.earlyProxyReferences.put(cacheKey, bean);
//按条件创建代理对象
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
如上面的代码所示,这段代码的主要目标就是判断提前暴露的Bean是否需要做动态代理,需要的话就会返回提前暴露的Bean的动态代理对象
那么这里为什么要去判断是否需要动态代理呢?考虑下面这种情况
1:如果这里不返回这个Bean的动态代理对象,但是这个Bean在后续的初始化流程中会存在动态代理:
举例:这里假设A依赖B,B又依赖A,此时B正在获取A提前暴露的引用,如果这时将A本身的地址引用返回给B,那么B里面就会保存A原始的地址引用,当B创建完成后,程序返回去创建A时,结果A在初始化的流程(initializingBean)中发生了动态代理,那么这时Spring容器中实际使用的是A的动态代理对象,而B却持有了原始A的引用,那么这时容器中就会存在A原始的引用以及A的动态代理的引用,从而产生歧义,这就是为什么需要提前去判断是否需要创建动态代理的原因,__这个原因的问题在于填充属性populateBean流程在初始化流程(initializingBean)之前,而创建动态代理的过程在初始化流程中
6.3 判断Bean的地址是否发生变化
Spring在Bean初始化之后,又判断了一下Bean初始化之后的地址是否发生了变化,其代码逻辑如下所示:
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
//判断是否触发了提前创建bean的逻辑(getEarlyBeanReference)
//如果有其他bean触发了提前创建bean的逻辑,那么这里就不为null
if (earlySingletonReference != null) {
//判断引用地址是否发生了变化
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
}
那么这里为什么需要在初始化之后继续判断Bean的地址是否发生了变化呢?
这是因为,如果存在循环依赖,同时Bean在初始化的流程(initializingBean)中又发生了额外的动态代理,例如,除了在**getEarlyBeanReference中发生的动态代理之外,还有额外的动态代理发生了,也就是发生了两次动态代理,那么这时Bean的地址与getEarlyBeanReference流程中产生的Bean的地址就不一样了,**这时如果不处理这种情况,又会出现Spring容器中同时存在两种不同的引用对象,又会造成歧义,所以Spring需要避免这种情况的存在
6.4 如果Bean地址发生变化则判断是否存在强依赖的Bean
Spring在Bean的创建过程中如果出现了上诉6.3节的情况时,Spring采取了下面的方法进行处理:
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
//获取该Bean依赖的Bean
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
//去除因为类型检查而创建的Bean(doGetBean方法typeCheckOnly参数来控制)
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
//如果去除因为类型检查而创建的bean之外还存在依赖的bean
//(这些剩下的bean就是spring实际需要使用的)那么就会抛出异常,阻止问题出现
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 " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
上面这段代码就是Spring处理上诉情况的逻辑,首先明确的是Spring不允许上诉情况发生,Spring对于Bean的引用地址发生变化的情况,Spring首先会判断依赖的Bean是否已经完全创建完毕,如果存在完全创建完成的Bean就会直接抛出异常,因为这些完全创建完成的依赖Bean中持有的引用已经是旧地址引用了
具体的处理逻辑是:先拿到该Bean所有依赖的Bean,然后从这些Bean中排除仅仅是因为类型检查而创建的Bean,如果排除这些Bean之后,还有依赖的Bean,那么这些Bean就是可能存在循环依赖并且是强依赖的Bean(这些Bean中持有的引用地址是老地址,所以会存在问题),Spring就会及时抛出异常,避免发生问题
作者:京东零售 钟磊
来源:京东云开发者社区 自猿其说Tech 转载请注明来源
带着问题去分析:Spring Bean 生命周期的更多相关文章
- Spring Bean 生命周期之destroy——终极信仰
上一篇文章 Spring Bean 生命周期之我从哪里来 说明了我是谁? 和 我从哪里来? 的两大哲学问题,今天我们要讨论一下终极哲学我要到哪里去? 初始化 Spring Bean 有三种方式: @P ...
- Spring5源码分析之Bean生命周期
Spring Bean生命周期的构成 Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类: Bean自身的方法: 这个包括了Bean本身调用的方法和通过配置文件中<bean&g ...
- 大厂高频面试题Spring Bean生命周期最详解
Spring作为当前Java最流行.最强大的轻量级框架.Spring Bean的生命周期也是面试高频题,了解Spring Bean周期也能更好地帮助我们解决日常开发中的问题.程序员应该都知道Sprin ...
- Spring Bean生命周期,好像人的一生。。
大家好,我是老三,上节我们手撸了一个简单的IOC容器五分钟,手撸一个Spring容器!,这节我们来看一看Spring中Bean的生命周期,我发现,和人的一生真的很像. 简单说说IoC和Bean IoC ...
- spring bean 生命周期和 ? 作用域? spirng bean 相互依赖? jvm oom ? jvm 监控工具? ThreadLocal 原理
1. spring bean 生命周期 1. 实例化一个bean ,即new 2. 初始化bean 的属性 3. 如果实现接口 BeanNameAware ,调用 setBeanName 4. Bea ...
- Spring点滴四:Spring Bean生命周期
Spring Bean 生命周期示意图: 了解Spring的生命周期非常重要,我们可以利用Spring机制来定制Bean的实例化过程. -------------------------------- ...
- 常见问题:Web/Servlet生命周期与Spring Bean生命周期
Servlet生命周期 init()初始化阶段 Servlet容器加载Servlet(web.xml中有load-on-startup=1;Servlet容器启动后用户首次向Servlet发请求;Se ...
- 睡前聊一聊"spring bean 生命周期"
spring bean 生命周期=实属初销+2个常见接口+3个Aware型接口+2个生命周期接口 实属初销:spring bean生命周期只有四个阶段,即实例化->属性赋值->初始化-&g ...
- Spring Boot 启动源码解析结合Spring Bean生命周期分析
转载请注明出处: 1.SpringBoot 源码执行流程图 2. 创建SpringApplication 应用,在构造函数中推断启动应用类型,并进行spring boot自动装配 public sta ...
- Spring Bean 生命周期之“我从哪里来?” 懂得这个很重要
Spring bean 的生命周期很容易理解.实例化 bean 时,可能需要执行一些初始化以使其进入可用 (Ready for Use)状态.类似地,当不再需要 bean 并将其从容器中移除时,可能需 ...
随机推荐
- 一行命令使用 Docker 编译 Latex 文件,简单优雅
使用 Docker 编译 LaTeX 文章 LaTeX 是一种常用的排版系统,它可以帮助用户创建漂亮.专业的文档.但是,安装和配置 LaTeX 比较麻烦,特别是对于初学者而言. Docker 是一个开 ...
- mysql高级进阶(存储过程、游标、触发器)
废话不多说,直接进入正题... 一.存储过程 a.概述 存储过程可以看成是对一系列 SQL 操作的批处理: 使用存储过程的好处 代码封装,保证了一定的安全性: 代码复用: 由于是预先编译,因此具有很高 ...
- Hexo博客使用valine评论系统无效果及终极解决方案
注意事项 有一些博主valine评论系统无效果,有一些原因: 1.很大程度是因为next的版本升级导致某些参数设置不同 2.valine评论是基于LeanCloud,还有一个文章阅读次数功能也是用Le ...
- Maven项目配置
pom.xml配置 配置编码格式为UTF-8 <properties> <project.build.sourceEncoding>UTF-8</project.buil ...
- python数据处理:获取Dataframe中的一列或一行
解决方案 df['w'] #选择表格中的'w'列,使用类字典属性,返回的是Series类型 df.w #选择表格中的'w'列,使用点属性,返回的是Series类型 df[['w']] #选择表格中的' ...
- node: #!/usr/bin/env node
声明 windows中不支持Shebang,它是通过文件的扩展名来确定使用什么解释器来执行脚本 参考链接: https://juejin.cn/post/6844903826344902670
- Cilium系列-16-CiliumNetworkPolicy 实战演练
系列文章 Cilium 系列文章 前言 今天我们进入 Cilium 安全相关主题, 基于 Cilium 官方的<星球大战> Demo 做详细的 CiliumNetworkPolicy 实战 ...
- 【RocketMQ】MQ消息发送总结
RocketMQ是通过DefaultMQProducer进行消息发送的,它实现了MQProducer接口,MQProducer接口中定义了消息发送的方法,方法主要分为三大类: send同步进行消息发送 ...
- CF-1860C Game on Permutation题解
题意:在一条数轴上,Alice可以跳到在你所在点前面且值比当前所在点小的点.每回合可以向任意符合要求的点跳一次.当轮到Alice的回合同时不存在符合要求的点,Alice就赢了.Alice可以选择一个点 ...
- 9、Mybatis之动态SQL
9.1.环境搭建 9.1.1.创建新module 创建名为mybatis_dynamicSQL的新module,过程参考5.1节 9.1.2.创建Emp实体类 package org.rain.myb ...