关于spring循环依赖说明
什么是循环依赖
多个对象相互持有对方引用形成一个闭环。
举例:
用一个简单举例,A依赖B,B依赖A情况。A–>B–>A
@Component
pubilc Class A{
@Autowired
private B beanB;
// getter & setter
}
@Component
public Class B{
@Autowired
private A beanA;
// getter & setter
}
spring解决循环依赖的思路
不管当前的bean有没有循环依赖,提前实例化,缓存刚实例化的bean,接着填充属性,再缓存完成填充属性的bean,此时的bean实例可用
为什么获取bean的对象的时候会出现实例化、再填充属性两部分,而不是一步完成呢?
可以简单理解为实例化就是先在内存中占个位子,即一个地址引用。spring在依赖注入的时候, 实际传递的是一个对象引用。在被依赖的时候,可以直接引用刚实例化的bean, 达到中断了被依赖的bean对它自己属性填充,从而避免了循环的发生。
以上述为例:当前beanA实例化,缓存实例化的beanA, 开始填充属性beanA,填充属性过程中发现依赖beanB,于是开始beanB的实例化,缓存实例化后的beanB, 接着开始填充属性beanB,填充属性的过程中发现依赖beanA,于是在缓存中找到了刚实例化的beanA, 然后依赖注入beanA,此时注入的是beanA的引用且不可用,这样beanB的填充属性就完成了。beanB的填充属性完成后,beanB实例就可用了,接着beanA完成了对beanB的依赖注入后,beanA自身的填充属性也完成了,beanA实例也可用了。那么此时beanB中的属性beanA是对象引用且可用。
简图描述:
如图示,在beanB依赖注入beanA时,如果没有将刚实例化的beanA放入缓存中,那么必然导致beanA实例化,填充属性的时候因为依赖beanB,又会实例化beanB,填充属性从而无限循环下去。而开始实例化beanB,填充属性的过程中发现依赖beanA,于是在缓存中找到了刚实例化的beanA,依赖注入beanA,此时注入的是beanA的对象引用,这样中断了在beanB中因为依赖beanA,再次出现beanA的实例化、填充属性的循环;这就是在getBean的时候为什么有实例化、填充属性两部分。
spring解决循环依赖借助了:提前实例化、填充属性两步,以及缓存。理解这些,有助于分析spring源码中对循环依赖的处理
spring能解决哪些循环依赖
spring 只解决单例setter循环依赖,对于原型模式,单例构造器注入的循环依赖没有解决,直接抛异常,spring中没有对原型模式的单例进行缓存,
在spring解决循环依赖的思路小结中理解了spring解决循环依赖借助了对象实例化、填充属性两步、缓存。那么就很容易理解spring为什么不能解决构造器注入的循环依赖。构造器在实例化阶段就需要持有对方引用,此时对象无法创建,也不存在。而对于原型模式的循环依赖,因为spring不缓存原型模式下的bean实例,所以也无解。
bean加载的初步流程
在理解了上述循环依赖说明后,再理解bean加载的循环依赖,只需要对bean加载有一个全局了解即可,所谓全局了解,不需要知道bean的加载详细的细节,关键是理解bean加载过程中几个重要的流程步骤。何时初始化bean、何时缓存bean、何时实例化bean.接下来看bean如何加载的
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
bean加载的逻辑
获取bean的逻辑主要在doGetBean中完成,现在将代码重要逻辑流程,梳理为逻辑代码,有个全局的思路,
注意:伪代码,注重bean的重要流程,去掉不重要代码,例如部分判断,try catch,
将用户输入的name装为beanName.为什么要转?name可能为bean的别名,也可能是&开头的名称
(重要)从缓存中获取单例
if(缓存中存在){
(重要)转为用户需要的最终实例,
}else{
如果缓存中没有,那么正常创建流程
if(当前bean为原型模式且创建中) 抛异常,循环依赖
当前容器没有bean信息,存在父类容器,那么从父类中获取bean信息
标记已经创建的bean
将xml配置存储的BeanDefinition转为RootBeanDefinition,如果beanName是子类,需要同时合并父类的相关属性,为什么要合并转为RootBeanDefinition?
depends-on的处理
根据不同作用域创建bean实例
if(单例){
(重要)获取bean
转为用户需要的最终实例,为什么要转?此处获取的bean可能是FactoryBean实例,那么需要转为FactoryBean.getObject()生产实例,也可能是用户需要的bean实例,直接返回
}else if(原型模式){
记录原型模式下,当前bean为创建中状态
创建bean实例
撤销当前bean为创建中状态
转为用户需要的最终实例
}else{
其他作用域bean的实例化,逻辑同原型模式
}
}
如果有提供bean类型,则进行类型转换
返回最终bean
bean加载的伪代码的实现
根据加载逻辑,在看看粗糙伪代码的获取bean的过程
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 提取对应的beanName
// 如果是FactoryBean,则去掉&前缀,再查看去掉&之后的name(可能为别名)是否有对应的beanName
final String beanName = transformedBeanName(name);
// 获取单例缓存中实例
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 返回对应的实例,已经生成了当前bean的实例,为什么不直接返回?
//当前实例bean可能是一个普通的bean,也可能是一个FactoryBean的实例,
// 如果是普通bean的实例,则直接返回,
//如果是FactoryBean的实例并且请求的name前面有&修饰,则说明客户端想要获取一个工厂的实例,那么直接返回
//如果是FactoryBean的实例并且请求的name前面没有&修饰,则要获取的工厂生产的bean的实例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} eles {
//只有在单例setter依赖注入情况才会尝试解决循环依赖,原型模式下,无解,不做处理,直接抛异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
//如果当前容器中不存在beanName,尝试从父容器parentBeanFactory中获取
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 省略逻辑
}
if (!typeCheckOnly) {
// 标记,将当前bean标记为已创建,存放到alreadyCreated缓存池中,并且需要重新mergedBeanDefinitions
markBeanAsCreated(beanName);
}
//将xml配置存储的BeanDefinition转为RootBeanDefinition,如果beanName是子类,需要同时合并父类的相关属性
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// 如果存在depends-on,则需要递归实例化依赖的bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
// 缓存当前beanName的依赖
registerDependentBean(dep, beanName);
// 对当前bean的依赖,进行实例化
getBean(dep);
}
}
// 实例化依赖的bean后,可以实例mgd本身了
if (mbd.isSingleton()) {
// 创建单例bean
sharedInstance = getSingleton(beanName, () -> {
//创建bean
// getSingleton(String beanName, ObjectFactory<?> singletonFactory)创建的实例,
// 最终依赖于singletonObject = singletonFactory.getObject();,也就是依赖singletonFactory,
// 而singletonFactory.getObject()实现是createBean,
return createBean(beanName, mbd, args);
});
// sharedInstance已经完成了初始化,属性填充
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
}
// bean的类型匹配
if (requiredType != null && !requiredType.isInstance(bean)) {
// 代码略
}
return (T) bean;
}
注意:markBeanAsCreated(beanName)中使用的alreadyCreated缓存池,在实例创建完成后,需要借助alreadyCreated缓存池做循环依赖检查
经过上述的流程,我想应该对bean实例化整体流程有了清晰的认识,
分析从缓存中获取bean的实例
现在具体深入了解下单例模式下,如何实例化bean
首先看看从缓存中获取实例
public Object getSingleton(String beanName) {
// 注意,第二个参数是否允许早期依赖循环引用
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// bean创建过程中借助了几个单例缓存池,如:singletonObjects、singletonsCurrentlyInCreation、earlySingletonObjects、singletonFactories
// 获取单例缓存中实例
// 在已经创建完成的单例bean的缓存中,获取当前bean是否已经创建
Object singletonObject = this.singletonObjects.get(beanName);
// 如果当前bean实例还未创建,并且当前bean正在创建中
// isSingletonCurrentlyInCreation(beanName)中使用逻辑,使用了singletonsCurrentlyInCreation缓存池
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从正在创建中bean(此时bean已经初始化,且完成了属性填充,但还未暴露)获取bean实例
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果没有并且当前单例bean允许早起循环引用
if (singletonObject == null && allowEarlyReference) {
// 从单例工厂缓存中获取创建bean的单例工厂,由单例工厂产生bean,问题:为什么存放单例工厂,而不直接是bean呢?
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 单例工厂不为空,则创建bean,并放入到
singletonObject = singletonFactory.getObject();
//此时singletonObject是一个早期刚初始化的bean,但并未填充属性,s属性扩展,代理等
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
实现逻辑,首先查找singletonObject单例缓存池,没有发现,此时bean还未创建成功,接着根据条件判断,没有在singletonObject缓存池中获取到实例且当前bean正在创建中,则从earlySingletonObjects缓存池中获取(this.earlySingletonObjects.get(beanName)),发现仍然没有, 接着从singletonFactories缓存池中查找,发现不为空并且允许早起循环引用,于是根据当前工厂获取bean实例,并将实例添加到earlySingletonObjects缓存池中,并从singletonFactories单例缓存池中移除
单例缓存池介绍
上述代码中出现了几个单例缓存池,每个单例缓存池作用,以及为什么需要这么多的单例缓存池呢?
首先看看每个单例缓存池的作用
- singletonObjects: 存放已经初始化的单例bean的实例
- earlySingletonObjects: 存放工厂bean刚创建bean实例,此时实例还未填充属性,代理、自定义业务逻辑等,
- singletonFactories:存放单例bean的工厂(用于创建单例)
- singletonsCurrentlyInCreation: 存放当前正在创建中的单例bean
- registeredSingletons: 存放所有实例化的bean
由上述的代码可以看出,查询缓存池顺序为:
|----bean创建完成--|----------bean创建中--------------------|
singletonObjects-->earlySingletonObjects-->singletonFactories
根据查询逻辑推断,bean创建过程中存放到单例缓存池的顺序依次:
|----------------创建中---------------|------创建完成-------|
singletonFactories-->earlySingletonObjects-->singletonObjects
上述单例缓存中查找单例逻辑,为什么这么实现?
主要是为了解决单例循环依赖,那么是如何借助这些单例缓存池解决循环依赖?
为了回答这个问题,我们必须明白什么时候用这些缓存池?接下里看看在哪些逻辑上使用了这些缓存池。是否如上述推断单例bean存放到缓存池中的顺序呢?
加载bean过程中是如何应用单例缓存池
回到主逻辑上,如果首次创建bean,那么肯定没有缓存,需要走正常创建bean的逻辑,找到单例模式下,创建bean的代码
sharedInstance = getSingleton(beanName, () -> {
try {
//创建bean
// getSingleton(String beanName, ObjectFactory<?> singletonFactory)创建的实例,
// 最终依赖于singletonObject = singletonFactory.getObject();,也就是依赖singletonFactory,
// 而singletonFactory.getObject()实现是createBean,
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// 异常处理
throw ex;
}
});
可以看出有两个参数,一个是beanName,一个是匿名的ObjectFactory类,其中匿名内部类中的有个回调方法ObjectFactory.getObject(),这个方法中的createBean(beanName, mbd, args)方法似乎实现了创建bean的逻辑,此处采用Lambda 表达式,那么getSingleton实现逻辑是什么呢?详细看看
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 记录beanName为正在加载状态 this.singletonsCurrentlyInCreation.add(beanName)
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
// 获取的可能是bean的实例,也可能是创建实例的ObjectFactory的实例
// singletonFactory.getObject()的实现逻辑参考createBean(beanName, mbd, args)
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (Exception ex) {
// 省略异常处理
throw ex;
} finally {
// bean创建完成后,
// 移除beanName为正在加载状态 this.singletonsCurrentlyInCreation.remove(beanName)
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 将实例放入singletonObjects、registeredSingletons缓存中,
// 并删除加载bean过程中的所记录的辅助状态 singletonFactories、earlySingletonObjects
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
从上面代码可以看出getSingleton方法有两个参数,beanName和ObjectFactory(创建bean的具体实现),
主要逻辑:
1、记录当前bean为正在创建状态
将当前正在创建中的bean添加到this.singletonsCurrentlyInCreation单例缓存池
2、回调匿名内部类ObjectFactory#getObject()方法创建实例,
创建bean过程中将创建bean的工厂添加到this.singletonFactories单例缓存池、
同时将当前bean添加到this.registeredSingletons单例缓存池中,标记bean已创建
此处只需要知道这一步操作里面有这些单例缓存池的应用场景,可以跳过阅读下面详细解释。如果需要知道singletonFactories、registeredSingletons单例缓存池的详细应用,参见下方创建bean准备工作,
回调匿名内部类ObjectFactory#getObject()方法创建实例具体逻辑,此方法中涉及到singletonFactories单例缓存池、registeredSingletons单例缓存池的应用,看看是如何应用的?
创建bean准备工作
对getSingleton匿名内部类回调方法代码的简化处理
singletonObject = singletonFactory.getObject(){
createBean(){
doCreateBean(){
// 实例化bean,将BeanDefinition转为BeanWrapper
instanceWrapper = createBeanInstance(beanName, mbd, args);
// bean合并后,应用后处理器,如:Autowired注解正式通过此方法实现诸如类型的预解析
// AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
// 是否允许单例提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 单例&允许循环依赖&正在创建中的单例
if(earlySingletonExposure){
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory){
// 将创建bean的singletonFactory,放入缓存
// getEarlyBeanReference,对bean再一次依赖引用,主要应用SmartInstantiationAwareBeanPostProcessor
// 我们熟悉的AOP就是在这里将advice动态织入bean中,如果没有,则直接返回bean,不做处理
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
// bean的属性填充,
populateBean(){
// 这里会将当前bean所需的依赖进行实例化,getBean(propertyValue),并填充
// 举例A类某字段依赖B,那么在A还未完成实例化前,在填充属性阶段,会优先实例化A的字段B,此时Abean存储在singletonFactories中
// 如果恰好B类中某字段依赖A,循环出现了,如何解决?
// 还记得上述首次获取单例的代码?依次从singletonObjects-->earlySingletonObjects-->singletonFactories
// 这样A类的依赖B就可以拿到自己的依赖A的实例,此处实例不是完全的,但与最终单例A的地址是一样的,当A实例完成后,那么B类中引用的A的实例也是全部完成实例化的
}
// 通过bean初始化进行扩展
initializeBean(){
// 为当前bean获取一些相对应的资源,调用Aware,如BeanFactoryAware,
invokeAwareMethods
// 在调用客户自定义初始化前,利用BeanPostProcessors自定义扩展,
applyBeanPostProcessorsBeforeInitialization
// 调用用户自定的初始化逻辑
invokeInitMethods{
// 优先调用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet()
// 调用用户自定义的init-method
invokeCustomInitMethod(beanName, bean, mbd);
}
// 在调用客户自定义初始化后,利用BeanPostProcessors自定义扩展
applyBeanPostProcessorsAfterInitialization
}
// 对已经实例化的bean进行循环依赖检查
if(earlySingletonExposure){
// 查看当前bean是否被询依赖,当前bean正在创建中,不会出现在singletonObjects缓存中,
// 假设当前bean没有被依赖,则当前bean实例一定在singletonFactories中,
// 如果被依赖,则当前bean实例因为需要加载,则会在缓存中查找,当查找后,会将singletonFactories中的工厂生产的实例存放在earlySingletonObjects单例缓存中,
// 为了检测是否有循环依赖,只需要查询earlySingletonObjects单例缓存中是否存在当前bean的实例,那么参数allowEarlyReference为false
Object earlySingletonReference = getSingleton(beanName, false){
// 省略相关单例缓存从中获取实例的逻辑,详情参看getSingleton()代码
if (singletonObject == null && allowEarlyReference) {
// 将创建bean的singletonFactory,放入缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
// 如果earlySingletonReference不为空,则发生了循环依赖,
// 那么需要检查当前实例与初始化后的实例是否一样,
// 如果一样,则可以直接返回使用,
// 如果不一样,则需要检查是否有未完成依赖的属性
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 获取当前bean的依赖
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
// 如果当前依赖并没有实例化,没有出现在已经创建实例的bean缓存中(!this.alreadyCreated.contains(beanName))
// 什么时候将bean记录到alreadyCreated中呢?参考AbstractBeanFactory.doGetBean-->AbstractBeanFactory.markBeanAsCreated
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 如果存在没有实例化的依赖,则说明当前bean实例化失败,
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.");
}
}
}
}
// 注册DisposableBean,如果配置了destroy-method,这里需要注册,便于在销毁bean调用
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
}
}
总结doCreateBean处理逻辑流程:
- 2.1、实例化bean,将BeanDefinition转为BeanWrapper
- 2.2、bean合并后的后处理器
- 2.3、允许单例提前暴露,将生产bean的bean工厂添加到singletonFactory单例缓存中
- 2.4、bean的属性填充,
- 2.5、通过bean初始化进行扩展
- 2.6、对已经实例化的bean进行循环依赖检查
- 2.7、注册DisposableBean,销毁bean时回调
对于单例循环依赖, 我们最关注的是第3、6步,第3步,将创建bean的bean工厂添加到singletonFactory单例缓存中,提前暴露单例,第6步、查看当前已经初始化的bean的是否有循环依赖
3、移除当前bean创建中状态
将当前正在创建中的bean从singletonsCurrentlyInCreation单例缓存池中移除
4、缓存当前创建完后的bean实例
将当前bean实例添加到singletonObjects单例缓存中
至此,涉及到的几个单例缓存池已经全部用到了,那么是如何解决单例setter循环依赖呢?
解决循环依赖
用一个简单举例,A依赖B,B依赖A情况,分析他们的加载过程,来回顾一下bean加载过程中,几个单例缓存池的应用,以及如何解决循环依赖的
@Component
pubilc Class A{
@Autowired
private B b;
// getter & setter
}
@Component
public Class B{
@Autowired
private A a;
// getter & setter
}
首次加载A的时候,没有缓存,先将beanA放入到singletonsCurrentlyInCreation单例缓存池,接着讲创建A的工厂放入到singletonFactories单例缓存池中,接着实例A在填充它的属性B的时候发现依赖B,于是getBean(beanB), 开始加载B
首次加载B的时候,没有缓存,先将beanB放入到singletonsCurrentlyInCreation单例缓存池,接着将创建B的工厂放入到singletonFactories单例缓存池中,接着实例B在填充它的属性A的时候发现依赖A,于是getBean(beanA), 开始加载A
第二次加载A,由于之前已经放到单例缓存池中了,所有进入缓存中查找,执行getSingleton(String beanName)–>getSingleton(beanName, true)的逻辑,第二参数允许早起循环引用,首先查找singletonObject单例缓存池,发现没有,因为此时beanA还未创建成功,接着根据条件判断,没有在singletonObject缓存池中获取到实例且当前beanA正在创建中,所以从earlySingletonObjects缓存池中获取(this.earlySingletonObjects.get(beanName)),发现仍然没有, 接着从singletonFactories缓存池中查找,发现不为空并且允许早起循环引用,于是根据当前工厂获取bean实例,并将实例添加到earlySingletonObjects缓存池中,并从singletonFactories单例缓存池中移除,
由于B的属性填充过程中获取到A的实例,虽然此时B属性中的实例A刚初始化,还未填充属性,但是它的地址和A的实例地址一样。这样就中断了对实例A的属性填充,从而避免了循环依赖
B完成属性填充后,进行初始化,进行依赖循环检查,则,B初始化完成,从singletonsCurrentlyInCreation单例缓存池中移除beanB,将beanB存放到singletonObjects,并记录beanB到registeredSingletons单例缓存池中,beanB已经实例化,那么接着继续A的属性填充,当A完成属性填充,初始化后,从singletonsCurrentlyInCreation单例缓存池中移除beanA,将beanB存放到singletonObjects,并记录beanA到registeredSingletons单例缓存池中,标识beanA已经实例化。
至此整个与循环依赖的加载完成。理解了bean加载过程中的循环依赖,那么对于bean的加载,我们就更加清晰