概述

在介绍 Spring 是如何解决循环依赖之前,先介绍一下什么是 Bean 的循环依赖,下面通过一个小示例介绍一下 Bean 的循环依赖

setter方式单例

我们分别创建两个类,A 和 B 它们之间相互依赖,如下代码

A 类

  1. @Component
  2. public class A {
  3. private B b;
  4. public A( B b){
  5. this.b = b;
  6. }
  7. public void getB(){
  8. System.out.println(b);
  9. }
  10. }

B 类

  1. @Component
  2. public class B {
  3. private A a;
  4. public B(A a){
  5. this.a = a;
  6. }
  7. public void getA(){
  8. System.out.println(a);
  9. }
  10. }

SpringCircleConfig 类

  1. @Configuration
  2. @ComponentScan("com.zlp.spring.circle.set")
  3. public class SpringCircleConfig {
  4. }

测试类

  1. public class AnnotationSetCircleTest {
  2. public static void main(String[] args) {
  3. ApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircleConfig.class);
  4. A a = ac.getBean("a",A.class);
  5. System.out.println(a.toString());
  6. a.getB();
  7. }
  8. }

首先回忆一下之前介绍的 Spring 初始化过程的内容,在 Spring 初始化 Bean 的过程中,有一个重要很重要的步骤就是属性赋值。这个属性赋值的过程简单来讲就是获取每一个Bean对象内部的属性,然后通过反射把属性值赋给这个Bean对象。对于基本类型的值,例如String、Integer等,很简单,直接解析赋值即可。

对于对象类型的值,例如上述示例中,它的处理过程是什么呢?

第一步:A对象初始化完毕,此时开始进行属性填充,发现 A中需要注入B,则按照创建Bean的流程去创建B

第二步:在创建 B的过程中,首先去初始化 B,然后进行属性填充,发现 B 中需要注入 A,则按照创建Bean的流程在去创建A;
Spring 源码分析-循环依赖(八) - 图1
按照上述的过程会发现创建带有依赖关系的 Bean 的过程就会变成一个死循环,一直转圈。但是上述的示例可以正常运行,这是因为 Spring 中通过引入了早期对象以及使用多级缓存解决了这个问题。

整体的工作流程如下

image.png

早期对象

什么是早期对象呢?

早期对象指的是 Bean 的一个半成品。更确切的说,就是实例化完成(没有初始化),并执行了一部分后置处理器,但是还未填充属性的对象,这个对象不能直接使用。例如对于上例中的 A 对应的早期对象其实就是一个空的 A 实例,如下:
Spring 源码分析-循环依赖(八) - 图3

如何使用这个早期对象?

这个早期对象 A 创建出来之后,会被保存在一个地方【Spring的二级缓存中】。在进行B实例的属性赋值过程中,发现依赖了A这个对象,然后就会去找A对象。没找到初始化完成的A对象会再去找这个早期的A对象,找到之后,直接完成B实例的属性赋值,将这个没有初始化完成的A对象赋值给B中的a属性,此时,B完成属性填充。
然后 A 中依赖了 B,A 属性填充过程中同样会将未初始化完成的B对象填充给b属性,至此,完成依赖Bean的属性填充。

使用半成品的对象进行属性填充会有问题吗?

不会有问题。在属性填充过程完成之后,会进行bean的初始化过程,初始化过程中,初始化的 bean对象就是这个半成品对象,因为属性赋值的过程其实就是把对象的引用赋值给依赖Bean的成员变量【反应在内存中就是同一个地址引用】。所以当这个对象被初始化之后,之前已经完成赋值的半成品对象自然就会变成初始化完成的对象。

多级缓存

底层代码是通过三级缓存实现,对应三个Map,代码过于繁多,具体可参考:
DefaultSingletonBeanRegistry.java类中

  1. /**
  2. * Cache of singleton objects: bean name to bean instance.
  3. * 一级缓存:单例对象的缓存,也被称作单例缓存池
  4. */
  5. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  6. /**
  7. * Cache of early singleton objects: bean name to bean instance.
  8. * 二级缓存:提前曝光的单例对象的缓存,用于检测循环引用
  9. */
  10. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  11. /**
  12. * Cache of singleton factories: bean name to ObjectFactory.
  13. * 三级缓存:保存对单实例bean的包装对象
  14. */
  15. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  16. /**
  17. * 按照注册顺序所保存的已经注册的单例对象的名称
  18. */
  19. private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
  20. /**
  21. * Add the given singleton object to the singleton cache of this factory.
  22. * <p>To be called for eager registration of singletons.
  23. * @param beanName the name of the bean
  24. * @param singletonObject the singleton object
  25. */
  26. protected void addSingleton(String beanName, Object singletonObject) {
  27. synchronized (this.singletonObjects) {
  28. // 将创建好的单实例bean放入到单例缓存池中
  29. this.singletonObjects.put(beanName, singletonObject);
  30. // 从三级缓存中删除
  31. this.singletonFactories.remove(beanName);
  32. // 从二级缓存中删除(早期对象:已经实例化,但是未完成属性赋值的对象)
  33. this.earlySingletonObjects.remove(beanName);
  34. // 保存到已注册单实例Bean名称集合中
  35. this.registeredSingletons.add(beanName);
  36. }
  37. }
  38. /**
  39. * 添加三级环境
  40. * <p>To be called for eager registration of singletons, e.g. to be able to
  41. * resolve circular references.
  42. * @param beanName the name of the bean
  43. * @param singletonFactory the factory for the singleton object
  44. */
  45. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  46. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  47. synchronized (this.singletonObjects) {
  48. // 如果当前的单实例缓存池中还没有beanName对应的单实例bean
  49. if (!this.singletonObjects.containsKey(beanName)) {
  50. // 将当前beanName对应的ObjectFactory放入到三级缓存singletonFactories中
  51. this.singletonFactories.put(beanName, singletonFactory);
  52. // 从早期的单例对象缓存中移除beanName对应的bean实例
  53. this.earlySingletonObjects.remove(beanName);
  54. // 将当前的beanName保存到已经注册的bean对应的Set集合中,标识其已经注册过
  55. this.registeredSingletons.add(beanName);
  56. }
  57. }
  58. }
  59. @Override
  60. @Nullable
  61. public Object getSingleton(String beanName) {
  62. // 从缓存中获取单实例Bean
  63. return getSingleton(beanName, true);
  64. }
  65. /**
  66. * Return the (raw) singleton object registered under the given name.
  67. * <p>Checks already instantiated singletons and also allows for an early
  68. * reference to a currently created singleton (resolving a circular reference).
  69. * @param beanName the name of the bean to look for
  70. * @param allowEarlyReference whether early references should be created or not
  71. * @return the registered singleton object, or {@code null} if none found
  72. */
  73. ////////////////////////////////////////////////////////////////////////////
  74. // ******该段代码是 Spring 解决循环引用的核心代码******
  75. //
  76. // 解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),
  77. // 并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存),
  78. // 通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,
  79. // 从而避免出现循环引用导致的死循环。
  80. //
  81. // 这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,
  82. // 该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。
  83. // 另外这个代码块中引进了4个重要缓存:
  84. // singletonObjects 缓存:beanName -> 单例 bean 对象。
  85. // earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。
  86. // singletonFactories 缓存:beanName -> ObjectFactory。
  87. // singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。
  88. // singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “三级缓存” 的概念。
  89. ////////////////////////////////////////////////////////////////////////////
  90. /**
  91. * 注意:
  92. * (1)通过 setter 注入方式产生的循环引用是可以通过以上方案解决的。
  93. * (2)构造器注入方式产生的循环引用无法解决,因为无法实例化出 early singleton bean 实例。
  94. * (3)非单例模式的循环引用也无法解决,因为 Spring 框架不会缓存非单例的 bean 实例。
  95. */
  96. @Nullable
  97. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  98. // 根据beanName从单实例对象缓存中获取单例对象(singletonObjects为一个ConcurrentHashMap,就是用来保存所有的单实例Bean的,
  99. // key:beanName value:beanInstance) 相当于一级缓存
  100. Object singletonObject = this.singletonObjects.get(beanName);
  101. // 如果缓存中不存在,而且beanName对应的单实例Bean正在创建中.
  102. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  103. // 加锁操作.
  104. synchronized (this.singletonObjects) {
  105. // 从早期单实例对象缓存中获取单例对象(之所以称为单实例早期对象,
  106. // 是因为earlySingletonObjects里面的对象都是通过提前曝光的ObjectFactory创建出来的,还未进行属性的填充)
  107. singletonObject = this.earlySingletonObjects.get(beanName);
  108. // 如果早期单实例对象缓存中没有,而且允许创建早期单实例对象引用
  109. if (singletonObject == null && allowEarlyReference) {
  110. // 则从单例工厂缓存中获取BeanName对应的单例工厂.
  111. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  112. if (singletonFactory != null) {
  113. // 如果存在着单例对象工厂,则通过工厂创建一个单例对象,
  114. // 调用的是:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))中的拉姆达表达式
  115. singletonObject = singletonFactory.getObject();
  116. // 将通过单例对象工厂创建的单例对象放入到早期单例对象缓存中,这个早期对象指的是一个空的未完成属性赋值和初始化的对象。
  117. this.earlySingletonObjects.put(beanName, singletonObject);
  118. // 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放入到earlySingletonObjects缓存中了,
  119. // 所以,后续通过beanName获取单例对象,可以通过earlySingletonObjects缓存获取到,不需要再用到该单例工厂.
  120. this.singletonFactories.remove(beanName);
  121. }
  122. }
  123. }
  124. }
  125. return singletonObject;
  126. }
  127. /**
  128. * Return the (raw) singleton object registered under the given name,
  129. * creating and registering a new one if none registered yet.
  130. * @param beanName the name of the bean
  131. * @param singletonFactory the ObjectFactory to lazily create the singleton
  132. * with, if necessary
  133. * @return the registered singleton object
  134. */
  135. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  136. // 校验bean的名称,不能为空
  137. Assert.notNull(beanName, "Bean name must not be null");
  138. synchronized (this.singletonObjects) {
  139. // 首先从单例缓存池singletonObjects【Map<String,Object>】中尝试获取单实例bean
  140. Object singletonObject = this.singletonObjects.get(beanName);
  141. // 如果未获取到,则通过如下的过程去创建单实例bean
  142. if (singletonObject == null) {
  143. /** 如果当前bean工厂中的实例bean正在被销毁,则不允许执行bean的创建过程 */
  144. if (this.singletonsCurrentlyInDestruction) {
  145. throw new BeanCreationNotAllowedException(beanName,
  146. "Singleton bean creation not allowed while singletons of this factory are in destruction " +
  147. "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
  148. }
  149. if (logger.isDebugEnabled()) {
  150. logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
  151. }
  152. /** 创建单实例bean之前的检查,根据名称校验当前的单实例bean是否正在创建中. */
  153. beforeSingletonCreation(beanName);
  154. boolean newSingleton = false;
  155. /** 初始化用来保存异常信息的Set集合 */
  156. boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
  157. if (recordSuppressedExceptions) {
  158. this.suppressedExceptions = new LinkedHashSet<>();
  159. }
  160. try {
  161. /** 回调ObjectFactory的getObject方法,进行单实例Bean的创建. */
  162. singletonObject = singletonFactory.getObject();
  163. /** 标注单实例bean创建成功 */
  164. newSingleton = true;
  165. }
  166. catch (IllegalStateException ex) {
  167. // Has the singleton object implicitly appeared in the meantime ->
  168. // if yes, proceed with it since the exception indicates that state.
  169. singletonObject = this.singletonObjects.get(beanName);
  170. if (singletonObject == null) {
  171. throw ex;
  172. }
  173. }
  174. catch (BeanCreationException ex) {
  175. if (recordSuppressedExceptions) {
  176. for (Exception suppressedException : this.suppressedExceptions) {
  177. ex.addRelatedCause(suppressedException);
  178. }
  179. }
  180. throw ex;
  181. }
  182. finally {
  183. if (recordSuppressedExceptions) {
  184. this.suppressedExceptions = null;
  185. }
  186. /** 创建完成之后,将bean名称从检查列表中删除. */
  187. afterSingletonCreation(beanName);
  188. }
  189. if (newSingleton) {
  190. /** 如果bean创建成功,将其加入单实例bean的map中 */
  191. addSingleton(beanName, singletonObject);
  192. }
  193. }
  194. return singletonObject;
  195. }
  196. }
  197. }

具体调用方法 AbstractBeanFactory.doGetBean方法中的 getSingleton(beanName)方法。

三级缓存对象

  1. singletonObjects:一级缓存,也被称为单实例缓存池,用来缓存所有初始化完成的Bean;
  2. earlySingletonObjects:二级缓存,用来保存早期对象,这个早期对象就是Bean的一个半成品对象,只完成了实例化化,未进行属性填充和初始化的对象;
  3. singletonFactories:三级缓存,用来保存获取早期对象的一个回调函数,通过这个回调函数,可以获取到未初始化完成的早期对象;

    查询流程

  • 根据Bean的名称首先查询一级缓存,查询到直接返回;
  • 未查询到单实例Bean而且允许使用早期对象,则查询二级缓存,查询到则直接返回;
  • 二级缓存中未查询到早期对象,则通过回调获取早期对象的方法获取早期对象,然后保存到二级缓存中并返回。

获取早期对象的实现可参考:
AbstractAutowireCapableBeanFactory.doCreateBean 中的 getEarlyBeanReference 方法。

获取 bean 调用流程方法

image.png


构造器参数循环依赖

构造器循环依赖

通过使用早期对象以及多级缓存解决了这种通过set方法注入时存在的循环依赖问题。但是通过构造器注入的方式无法解决,因为通过构造器的方式无法产生早期对象。如下示例:

A类

  1. @Component
  2. public class A {
  3. private B b;
  4. public A(@Lazy B b){
  5. this.b = b;
  6. }
  7. public void getB(){
  8. System.out.println(b);
  9. }
  10. }

B类

  1. @Component
  2. public class B {
  3. private A a;
  4. public B(@Lazy A a){
  5. this.a = a;
  6. }
  7. public void getA(){
  8. System.out.println(a);
  9. }
  10. }

测试类不变,运行之后,会提示如下异常:

BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?

解决方式

构造器方式注入产生的循环依赖,可以通过指定 Bean 的延迟加载,先注入代理对象,然后在需要使用这个bean的时候再去真实创建,如下:

A类

  1. @Component
  2. public class A {
  3. private B b;
  4. public A(@Lazy B b){
  5. this.b = b;
  6. }
  7. public void getB(){
  8. System.out.println(b);
  9. }
  10. }

B类

  1. @Component
  2. public class B {
  3. private A a;
  4. public B(@Lazy A a){
  5. this.a = a;
  6. }
  7. public void getA(){
  8. System.out.println(a);
  9. }
  10. }

通过其他例如@PostConstruct,或者使用后置处理器的方式也可以解决,但是如果真有这种循环依赖问题,建议还是使用最简单,也最容易理解的set方式去注入,因为这种方式框架本身就已经解决了这种问题。

setter方式原型,prototype

我们在A类和B类中,添加 @Scope(value = “prototype”) 注解

  1. @Component
  2. @Scope(value = "prototype")
  3. public class A {
  4. @Autowired
  5. private B b;
  6. public void getB(){
  7. System.out.println(b);
  8. }
  9. }

scope=”prototype” 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。

测试从新启动报错

  1. Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a';
  2. nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
  3. Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

为什么原型模式就报错了呢 ?

对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

几个 QA?

三级缓存放流程

image.png

QA 问题

  1. 三级缓存存对象,在获取数据的时候什么顺序来获取的

先获取一级缓存,没有在获取二级缓存,没有再获取三级缓存,所以当前面的缓存中存在了对象
级缓存。

  1. 一级能解决循环依赖问题

有一级缓存,那么成品对象和半成品对象会放到一起,这个是没办法区分了,所以需要两个缓存来分别存放不同状态的对象,一级缓存放成品,二级缓存放半成品。

3、如果只有二个缓存,能否解决循环依赖问题?
在刚刚的整个流程中,三级缓存一共出现了几次? getsingleton, doCreateBean
如果对象的创建过程中不包含aop,那么二级缓存就可以解决循环依赖问题,但是包含aop的操作,
循环依赖问题是解决不了的。

4、为什么添加了aop的操作之后就需要添加三级缓存来解决这个问题?三级缓存加了什么操作?
添加了一个 getEarlyBeanReference 的方法在创建代理对象的时候,

是否需要生成原始对象?
需要当创建完成原始对象之后,后续有霃要创建代理对象,那么对象在引用的时候应该使用哪一个
换句话说,就是一个 beanName对应有两个对象,(原始对象和代理对象在整个容器中,有且仅能有一个同名的对象,当需要生成代理对象的时候,就要把代理对象覆盖原

程序是怎么知道在什么时候要进行代理对象的创建的呢?
需要一个类似于回调的接口判断,当需要第一次对外暴露使用的时候,来判断当前对象是否需要去
创建代理对象, getEarlyBeanReference 方法的判断,如果需要代理就返回代理对象,如果没有代
理就返回原始对象。


参数文档

  1. Spring循环依赖的三种方式
  2. Spring如何解决bean的循环依赖?