Spirng源码解析之循环依赖

Spring循环依赖

一、java基础中的循环依赖

在java中其实并不存在所谓的循环依赖

A a = new A();
B b = new B();
a.setB(b);
b.setA(a);

这种其实是没有任何问题的,但是在Spring中就可能会存在着问题。

二、循环依赖解决思路

前提条件:单线程条件

V1

bean A 的完整生命周期

1、实例化A
2、填充B属性--->前往单例池中寻找B---->没有则创建B;
3、填充其他属性;
4、初始化前、初始化
5、初始化后
6、放入到单例池
bean B 的完整生命周期

2.1、实例化B
2.2、填充A属性--->前往单例池中寻找A---->没有则创建A;【如果不把这个问题处理好,就会出现递归。继续去创建A,就又会出现循环创建】
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池

解决方式:利用一个Map<beanName,对象>来进行缓存已经创建好了的实例

V2

bean A 的完整生命周期

1、实例化A;
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B;
3、填充其他属性;
4、初始化前、初始化;
5、初始化后;
6、放入到单例池
bean B 的完整生命周期

2.1、实例化B
2.2、填充A属性--->前往单例池中寻找A------>发现A在Map<A_name,A>中,赋值,执行下一步
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池

似乎已经完美的解决了问题。

但是这个时候可能会存在一个问题:在Bean B中,在给A填充属性之后,完成了操作,放入了单例池中。而Bean A在执行初始化之后,如果出现A发生了动态代理,放入了单例池中。那么就不合理了。

因为如果单例池中放入的是代理对象,而Bean B中的属性A是原始对象,不符合单例的概念。

解决方式:将动态代理提前化

V3

bean A 的完整生命周期

1、实例化A--->判断是否需要AOP?如果需要那么执行AOP逻辑----->代理对象--->Map<A_name,A代理对象>
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充其他属性;
4、初始化前、初始化
5、初始化后 
6、放入到单例池
bean B 的完整生命周期

2.1、实例化B--->判断是否需要AOP?如果需要那么执行AOP逻辑----->代理对象--->Map<B_name,B代理对象>
2.2、填充A属性--->前往单例池中寻找A------>发现A在Map<A_name,A代理对象>中,赋值,执行下一步
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池

那么现在就有几个问题:

1、怎么判断当前Bean是否需要经过AOP?判断逻辑是什么?AOP到底在哪里执行更加合适呢?

2、在出现循环依赖的情况下,要想解决循环依赖,就不得不得考虑到AOP,要考虑到AOP,就不得不去提前进行AOP。

那么重点首先就转移到了如何知道出现了循环依赖

解决方式:利用一个集合Map来记录一下正在创建的bean,存储bean的名称和相应的对象。

V4

bean A 的完整生命周期

1、实例化A--->记录A正在创建中(集合保存,以set举例)----->
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充其他属性;
4、初始化前、初始化
5、初始化后 
6、放入到单例池
bean B 的完整生命周期

2.1、实例化B--->记录B正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖] ------>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池

问题:

那么现在问题就转移到了:

1、AOP到底在哪里应该来执行?
2、对于需要和不需要经过AOP的bean如何使用逻辑处理?A出现AOP,需要将代理对象赋值给B;没有出现AOP,出现将原生对象赋值给B

V5

如果此时此刻,又有一个bean C也依赖了A的情况

bean A 的完整生命周期

1、实例化A--->记录A正在创建中(集合保存,以set举例)----->
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充C属性--->前往单例池中寻找C------>Map<C_name,C>---->没有则创建C;
4、初始化前、初始化
5、初始化后 
6、放入到单例池
bean B 的完整生命周期

2.1、实例化B--->记录B正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖]  ------>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
bean C 的完整生命周期

2.1、实例化C--->记录C正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AC出现循环依赖]  ------>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象    
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池

问题:bean B 和 bean C 都依赖了bean A,假如说B在进行属性注入的时候,A经历了一次AOP;在C进行属性注入的时候,A还要进行一次AOP?

解决方式:在B和C属性填充阶段,需要保证BC依赖的是同一个代理对象,不能够重复生成代理对象。因为bean B依赖了bean A ,那么在属性注入阶段,需要将A得到的最终结果存起来,如Map集合;因为bean C也依赖了bean A,那么在属性注入阶段,可以直接从Map中来进行获取得到bean A。

V6

bean A 的完整生命周期

1、实例化A--->记录A正在创建中(集合保存,以set举例)-----> 是否需要经过AOP?①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充C属性--->前往单例池中寻找C------>Map<C_name,C>---->没有则创建C;
4、初始化前、初始化
5、初始化后 
6、放入到单例池
bean B 的完整生命周期

2.1、实例化B--->记录B正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖]------>从Map缓存中查找是否已经创建好了-->没有-->>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象,最终放入到map中
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
bean C 的完整生命周期

2.1、实例化C--->记录C正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖]------>从Map缓存中查找是否已经创建好了-->没有-->>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象,最终放入到map中
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池

所以现在,在Map缓存中保存的是没有经历过完整的生命周期的bean;

问题:不管是否需要进行AOP,那么都需要有原始对象。代理对象中的target属性就是原始对象,那么原始对象如何来进行获取呢?

解决方式:1、ThreadLocal;2、通过参数形式;3、利用map

那么这里最好的方式是利用Map

V7

bean A 的完整生命周期

1、实例化A--->记录A正在创建中-----> 是否需要经过AOP?①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象 【map集合保存,key=beanName,value=lambda表达式,对应逻辑是:①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象】----------------->利用BeanPostProcess来进行判断

2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充C属性--->前往单例池中寻找C------>Map<C_name,C>---->没有则创建C;
4、初始化前、初始化
5、初始化后 
6、放入到单例池
bean B 的完整生命周期

2.1、实例化B--->记录B正在创建中															  				   执行lambda表达式得到结果
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖]  ------> map集合查询--> 肯定能够在map中找到--->list集合保存
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
bean C 的完整生命周期

2.1、实例化C--->记录C正在创建中															 			执行lambda表达式得到结果
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AC出现循环依赖]  ------> map集合查询--> 肯定能够在map中找到--->list集合保存
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池

总结

其实Spring的做法也是如此,那么我们回头来总结一下在上面用的缓存集合中在Spring的具体体现:

一级缓存:singletonObjects:单例池

二级缓存:earlySingletonObjects:不管是原始对象还是代理对象,都是没有经历过完整的生命周期的Bean!作用:保证单例

三级缓存:singletonFactories:保存lambda表达式。作用:打破循环依赖。利用map存了一个,在其他bean在依赖注入的时候能够来进行使用

解决循环依赖的最重要的一个是二级缓存:earlySingletonObjects,里面保存的是没有经历过完整的生命周期的bean。

三、循环依赖源码分析

AService和BService出现循环依赖

@Component
public class AService {

  @Autowired
  private BService bService;

  public void test(){
    bService.aaaa();
  }
}
@Component
public class BService {

  @Autowired
  private AService aService;

  public void aaaa(){
    System.out.println("bservice  aaaa........");
  }
}

3.1、创建AService过程

直接来到org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中

1、创建AService实例

2、循环依赖判断阶段

直接来到实例化过程后,进行循环依赖的判断:

当前判断标记为:当前bean为单例allowCircularReferences(Spring默认支持循环依赖)以及当前bean正在创建中,才会进入到下面的if条件中来。

那么表示当前bean正在创建中是在哪里记录的呢?在上面一步:org.Springframework.beans.factory.support.AbstractBeanFactory#doGetBean

然后进入到getSingleton方法

在这里将当前正在创建的AService的BeanName添加到集合singletonsCurrentlyInCreation中,记录当前bean正在创建中。

所以当前singletonsCurrentlyInCreation中至少有个beanName为AServcie

当前创建的是AService,所以可以进入到下方的判断中来

3、循环依赖保存lambda表达式阶段

因为正在创建AService,所以单例池中肯定是不包含的。然后将lambda表达式保存到三级缓存中。

看一下lambda表达式的保存,是保存到三级缓存中

4、属性填充阶段

在AService填充BService期间,BService是没有实例化的

在AService填充BService期间,BService是没有实例化的

在AService填充BService期间,BService是没有实例化的

在AService实例属性填充阶段中,调用org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法,因为在AService中使用的是@Autowired注解标注BServcie的,那么肯定会走AutowiredAnnotationBeanPostProcessor中的属性填充阶段

然后接着向下走

这里有一个步骤是针对于@Lazy注解的解析处理。表示的是如果属性上同时存在@Lazy注解,那么这里会先赋值一个临时值,然后返回

当前在AService类中有一个BService属性,只有@Autowired注解,没有@Lazy注解,那么看下如何来进行处理的。

继续到下一步骤中

给BService进行赋值的时候,会从容器中来进行查找BService类型的bean,会调用doGetBean方法。

然后进去,查看下具体的方法

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

注意这里的参数:beanName和true

但是因为BService并没有进行实例化过,所以不在单例池中,也不在创建中,所以直接返回null。

那么接下来就开始来进行BService的创建过程。

3.2、创建BService过程

直接来到org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中

1、创建BService实例

2、循环依赖判断阶段

直接来到实例化过程后,进行循环依赖的判断:

当前判断标记为:当前bean为单例allowCircularReferences(Spring默认支持循环依赖)以及当前bean正在创建中,才会进入到下面的if条件中来。

那么表示当前bean正在创建中是在哪里记录的呢?在上面一步:org.Springframework.beans.factory.support.AbstractBeanFactory#doGetBean

然后进入到getSingleton方法

在这里将当前正在创建的AService的BeanName添加到集合singletonsCurrentlyInCreation中,记录当前bean正在创建中。

所以当前singletonsCurrentlyInCreation中至少有两个个beanName为AServcie、BServcie

当前创建的是BService,所以可以进入到下方的判断中来

3、循环依赖保存lambda表达式阶段

因为正在创建BService,所以单例池中肯定是不包含的。然后将lambda表达式保存到三级缓存中。

看一下lambda表达式的保存,是保存到三级缓存中

4、属性填充阶段

在BService实例属性填充阶段中,调用org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法,因为在AService中使用的是@Autowired注解标注AServcie的,那么肯定会走AutowiredAnnotationBeanPostProcessor中的属性填充阶段

然后接着向下走

这里有一个步骤是针对于@Lazy注解的解析处理。表示的是如果属性上同时存在@Lazy注解,那么这里会先赋值一个临时值,然后返回

当前在BService类中有一个AService属性,只有@Autowired注解,没有@Lazy注解,那么看下如何来进行处理的。

继续到下一步骤中

给AService进行赋值的时候,会从容器中来进行查找BService类型的bean,会调用doGetBean方法。

然后进去,查看下具体的方法

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

注意这里的参数:beanName和true

那么此时分为了以下几个步骤:

  • 1、从单例池中获取,单例池中没有,返回null;
  • 2、AService正在创建中且支持循环引用;
  • 3、从二级缓存中尝试获取,获取不到,返回null;
  • 4、上面都不成立,那么一定在三级缓存中。因为此时的beanName为AService,那么执行AService保存进去的lambda表达式;
  • 5、将lambda表达式执行的结果放入到二级缓存中去;
  • 6、返回执行lambda表达式之后的结果;

从这里可以看到,如果没有循环引用,那么根本就不会执行到从三级缓存中获取得到lambda表达式来进行执行。

4.1、执行AService的lambda表达式

那么看下保存到三级缓存中的lambda表达式的执行。

找到SmartInstantiationAwareBeanPostProcessor类型的BeanPostProcessor,而当前只有一个AbstractAutoProxyCreator对应的实现类。所以直接看AbstractAutoProxyCreator类中的getEarlyBeanReference方法:

然而当前只有org.Springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference有具体的实现,其他的没有实现。

注意一下这里几个参数:

  • bean:表示的是AService根据构造方法创建出来的实例对象;
  • beanName:表示的是AService对应的bean的名称,当前beanName为AService

计算缓存key,然后放入到缓存中去。

4.2、为什么要缓存?

在我们分析循环依赖解决思路的过程中,我们提到过一点:已经提前进行了AOP,那么在初始化后的阶段中就不需要再来进行AOP了。

假如说:AService在属性填充阶段中,发现BService没有创建,那么在BService进行属性填充阶段中,发现AService需要进行AOP,那么AService进行AOP。

BService属性注入完成之后,执行初始化前、初始化、初始化后方法(当前BService不需要进行AOP)然后完成BService的生命周期。那么此时就来到了AService属性填充完成之后的阶段,AService也需要来执行初始化前、初始化、初始化后方法,在执行初始化后方法中,是否还需要对AService进行AOP呢?肯定是不需要的,因为在BService对AService进行属性填充的时候,已经进行了AOP。那么这个时候就需要来做一个记录。

不妨来看一下初始化后的方法(这个方法也在这个类中)

首先也是判断,循环依赖中有没有添加过,如果添加过,那么就不再走初始化后的判断是否需要进行AOP的步骤了。没有没有添加过,那么获取得到的肯定是null,那么肯定是不相同的,会进去判断当前bean是否需要经历AOP。

这就是上面添加到缓存中的目的。

4.3、lambda表达式代理逻辑

看一下wrapIfNecessary方法的注释,表示了:即如果它有资格被代理,包装一下给定的bean。

判断一下当前bean(即BService是否需要进行AOP),因为当前没有定义一些Advisor和Advice,所以没有任何代理逻辑。如果存在代理逻辑,那么在advisedBeans(Map<Object, Boolean>)中记录一下false(不需要代理),方便下次来进行查找。

对于当前BService来说,因为没有代理逻辑,所以直接返回原始BService类型的对象。

然后返回,成功进行赋值

5、初始化后方法

BService执行完属性填充阶段之后,然后马上来进行初始化后方法。来到org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.Springframework.beans.factory.support.RootBeanDefinition)方法中来

而当前只有org.Springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization具有对应的代理逻辑

BService中没有保存到earlyProxyReferences中,那么也会进入到代理逻辑中去来进行判断是否需要进行AOP。

因为BService没有保存到这个集合中,返回值为null,而Null != bean,所以进去判断是否需要来进行AOP。

从这里可以看到,如果对于已经在属性赋值阶段中完成AOP的,那么这里返回的将会是原始对象,而不是代理对象

而对于没有完成AOP的bean,那么执行到这里的时候,对于需要进行AOP的返回的是代理对象。

5.1、初始化后的BService保存到单例池

对于BService来说,earlySingletonReference为null,然后方法最终返回的是exposedObject。

3.3、AService执行初始化后方法

至此BService执行结束。但是因为AService是在属性填充阶段中来进行创建BService,那么BService在创建完成之后,还需要返回到之前的方法中来进行执行AService的初始化后方法。

对于AService来说

那么在初始化后直接返回原始bean对象

AService如何获取得到对象呢?

加入说AService是经过AOP的,那么应该获取得到的是代理对象,而这里获取得到的原始对象。那么就应该在下面来进行获取得到真实的对象

直接从二级缓存中获取得到的对象,然后来进行比较。

如果相等,那么就直接返回;如果不相等,那么就进行判断决定是否抛出异常信息。

对于当前AServcie对应的bean来说,肯定是相等的,那么就直接返回。

因为AService是在循环依赖中,判断添加到三级缓存转移到二级缓存中去的,肯定是经过了代理逻辑的判断,但是实际上是没有对应的代理方式的,所以直接返回的是原始bean对象。原始对象和原始对象相比,肯定是相同的。

四、循环依赖特殊情况

4.1、现象产生

上面也说到两个对象相等的情况,那么什么情况下两个是不相同的呢?

首先在配置类上开启异步任务配置

@ComponentScan(basePackages = "com.guang")
// 开启AOP
@EnableAspectJAutoProxy(exposeProxy=true)
@EnableAsync
public class LiAppCOnfig {}

对应的Service

@Component
public class AService {
  @Autowired
  private BService bService;

  @Async
  public void test(){
    bService.aaaa();
  }
}
@Component
public class BService {
  @Autowired
  private AService aService;

  public void aaaa(){
    System.out.println("bservice  aaaa........");
  }
}

那么在启动的时候就会来报错!!!

那么需要看下我们的注解@EnableAsync做了什么事情,这个注解配置了一个AsyncAnnotationBeanPostProcessor,而且是一个BeanPostProcessor。

打上断点观察一下,初始化后方法那里现在BeanPostProcessor的执行顺序:

那么就意味着,本来不走org.Springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization中的代理逻辑了,但是因为新加了一个AsyncAnnotationBeanPostProcessor,会进入到这个方法中的代理逻辑中来。

从而导致了AService再次被代理,所以返回的值和原始对象是不相同的。

所以抛出了对应的异常信息。

4.2、解决问题

在AService填充BService属性中,因为造成了BService创建,而BService在填充AService属性中,因为从单例池中获取不到AService对象,而执行了对应的lambda表达式,又执行了AsyncAnnotationBeanPostProcessor,从而导致AService失败。

那么问题就在于,让BService在创建的时候不走后续逻辑,先给BServuce一个临时值,先让AService先完成生命周期流程,那么保存到单例池中的就是最终的结果。那么应该想到了。

@Lazy注解

如下所示:

@Component
public class AService {
  @Autowired
  @Lazy
  private BService bService;

  @Async
  public void test(){
    bService.aaaa();
  }

  public BService getbService() {
    return bService;
  }
}

而在test方法中,使用bService.aaaa()方法时,这个时候,会从容器中查找得到真实的bServcie所对应的对象。

因为AService在进行属性填充时,没有走BService的创建流程;那么在BService在创建中时,要进行属性填充了,发现单例池中是存在AService的,那么就可以直接进行进行属性注入了。

posted @ 2022-06-18 20:22  雩娄的木子  阅读(16)  评论(0编辑  收藏  举报