Spring循环依赖

释放双眼,带上耳机,听听看~!

循环依赖

  • 首先要清楚Spring中bean的注入方式有:构造方法注入、Setter注入、静态工厂注入,常用的主要是前两种

  • 循环依赖指的是BeanA依赖于BeanB,BeanB依赖于BeanC,BeanC有依赖于BeanA,从而构成了一个环的情景。对应于bean的注入方式,也就有构造方法注入循环依赖,Setter注入循环依赖,静态工厂的不考虑。

  • 对于bean的作用域通常又有单例和多例区分,因此,构造方法注入循环依赖又可以再分为单例构造方法注入循环依赖和多例构造方法循环依赖,Setter注入循环依赖分为单例setter注入循环依赖和多例setter注入循环依赖。

单例
单例
多例
多例

  • Spring中提供了对单例setter注入循环依赖的解决方案,构造方法注入场景单例,多例都不支持。这里需要详细讲述原理。

  • 为什么无法解决构造方法注入循环依赖?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1   <!--单例构造方法-->
2   <bean id="beanA" class="com.larry.circleCons.BeanA" scope="singleton">
3       <constructor-arg ref="beanB"/>
4    </bean>  
5    <bean id="beanB" class="com.larry.circleCons.BeanB" scope="singleton">
6       <constructor-arg ref="beanC"/>
7    </bean>  
8    <bean id="beanC" class="com.larry.circleCons.BeanC" scope="singleton">
9       <constructor-arg ref="beanA"/>
10    </bean>  
11    或者多例:
12  <bean id="beanA" class="com.larry.circleCons.BeanA" scope="prototype">
13      <constructor-arg ref="beanB"/>
14    </bean>  
15    <bean id="beanB" class="com.larry.circleCons.BeanB" scope="prototype">
16      <constructor-arg ref="beanC"/>
17    </bean>  
18    <bean id="beanC" class="com.larry.circleCons.BeanC" scope="prototype">
19      <constructor-arg ref="beanA"/>
20    </bean>    
21
22

无论是单例还是多例,都会在通过getBean获取bean实例时,抛出异常,只不过由于单例默认有预加载机制,因此如果没有配置延迟加载那么将在Spring初始化过程中加载单例,抛出异常:


1
2
3
4
5
6
7
1 @org.junit.Test
2    public void testCircleRef() {
3       context = new ClassPathXmlApplicationContext("spring.xml");
4       context.getBean("beanA");
5    }
6
7

上述代码执行过程中,将抛出如下异常:


1
2
3
4
1org.springframework.context.support.AbstractApplicationContext refresh
2  警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'beanB' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'beanC' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanC' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'beanA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
3
4

那么Spring中,是如何检测构造方法注入循环依赖的呢?

Java中字段的初始化顺序是先静态字段,然后实例字段,然后才是构造方法,也就是说如果我们使用了构造方法注入来初始化实例对象,那么构造方法的入参对应的对象必须先于当前对象初始化完毕才行,如果是构造方法依赖注入:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1public BeanA(BeanB beanB) {
2       super();
3       this.beanB = beanB;
4}
5
6public BeanB(BeanC beanC) {
7       super();
8       this.beanC = beanC;
9}
10
11public BeanC(BeanA beanA) {
12      super();
13      this.beanA = beanA;
14}
15
16

实际上会形成这样的一种循环:

Spring循环依赖

BeanA->BeanB-BeanC->BeanA,最终就是BeanA要先于BeanA初始化,形成了悖论,因此Spring无法解决这种循环依赖,抛出异常。

可以看下Spring构造方法注入,实例化bean时的调用栈:

Spring循环依赖

从Spring的调用栈中可以很清晰的看到bean的初始化流程,也就是说BeanA的实例化之前,首先解析了构造参数入参的beanB,对BeanB的实例化又触发了对BeanC的实例化,直至最后检测到了循环。对于单例循环依赖使用了线程安全的ConcurrentHashMap。


1
2
3
4
5
1/** Names of beans that are currently in creation. */
2   private final Set<String> singletonsCurrentlyInCreation =
3           Collections.newSetFromMap(new ConcurrentHashMap<>(16));
4
5

1
2
3
4
5
6
7
8
9
10
11
12
13
1/**
2    * Callback before singleton creation.
3    * <p>The default implementation register the singleton as currently in creation.
4    * @param beanName the name of the singleton about to be created
5    * @see #isSingletonCurrentlyInCreation
6    */
7   protected void beforeSingletonCreation(String beanName) {
8       if (!this.inCreationCheckExclusions.contains(beanName) &&                  !this.singletonsCurrentlyInCreation.add(beanName)) {
9           throw new BeanCurrentlyInCreationException(beanName);
10      }
11  }
12
13

对于prototype作用域的bean来说,原理类似,如下图所示:

Spring循环依赖

只不过prototype使用了ThreadLocal来保存当前线程正在创建的bean名称,通过这个threadLocal来检测循环依赖:


1
2
3
4
5
1/** Names of beans that are currently in creation. */
2   private final ThreadLocal<Object> prototypesCurrentlyInCreation =
3           new NamedThreadLocal<>("Prototype beans currently in creation");
4
5

1
2
3
4
5
6
7
8
9
10
11
12
1   /**
2    * Return whether the specified prototype bean is currently in creation
3    * (within the current thread).
4    * @param beanName the name of the bean
5    */
6   protected boolean isPrototypeCurrentlyInCreation(String beanName) {
7       Object curVal = this.prototypesCurrentlyInCreation.get();
8       return (curVal != null &&
9               (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
10  }
11
12

以上分析了Spring对单例和多例构造方法注入循环依赖的检测机制,我们可以做一个总结:

对bean实例化,都需要先实例化构造方法参数的引用,这个流程是一样的

都是在调用getBean()方法获取实例时触发依赖注入

使用ConcurrentHashMap持有当前正在创建的bean
使用ThreadLocal持有当前正在创建的bean

我们也可以看到Spring采用了不同的缓存来持有单例和多例当前正在创建的bean。为什么Spring要采用不同的缓存呢?


作用域:单例的bean在整个Spring容器的生命周期只有一份,多例则是每次调用都会创建新的实例。Spring对单例的初始化有预初始化和延迟初始化两种,预初始化是随着Spring容器的初始化而初始化的,那么这里的线程是调用Spring容器的线程,而延迟初始化,指的是显示通过getBean()方法进行初始化,这里的线程就是调用getBean的线程,多个线程同时调用对于单例,为了保证其全局唯一,因此必须要用线程安全的容器来保证,对于多例,本来每次都会创建新的实例,因此它只要保证我当前线程调用能够看到自己的创建链就可以了,这正是ThreadLocal的最佳用武之地。


  • Spring如何检测多例setter注入循环依赖?


1
2
3
4
5
6
7
8
9
10
11
1    <bean id="beanA" class="com.larry.circleCons.BeanA" scope="prototype">
2       <property name="beanB" ref="beanB"></property>
3    </bean>  
4    <bean id="beanB" class="com.larry.circleCons.BeanB" scope="prototype">
5       <property name="beanC" ref="beanC"></property>
6    </bean>  
7    <bean id="beanC" class="com.larry.circleCons.BeanC" scope="prototype">
8       <property name="beanA" ref="beanA"></property>
9    </bean>
10
11

Spring循环依赖

从上图可以看出,setter注入和构造方法注入的依赖注入入口不一样,setter注入的入口是populateBean()方法,这里触发了依赖对象的实例化,而构造方法是autowireConstructor()方法。对于多例Spring其他地方的检测方法与构造方法检测是一样的。不再赘述。

  • Spring如何解决单例setter注入循环依赖问题?

Spring循环依赖
上图是单例setter注入,实例化的方法调用栈,可以看到单例setter注入的入口方法同多例一样仍然是populateBean()方法,调用流程实际上和多例的没有太大区别,但是当beanA调用栈又回到beanA时,Spring并没有报错,那么Spring怎么解决了单例setter注入的循环依赖问题?
在Sring org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(String, RootBeanDefinition, Object[])方法中存在这样一段代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1   // Eagerly cache singletons to be able to resolve circular references
2           // even when triggered by lifecycle interfaces like BeanFactoryAware.
3           boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
4                   isSingletonCurrentlyInCreation(beanName));
5           if (earlySingletonExposure) {
6               if (logger.isTraceEnabled()) {
7                   logger.trace("Eagerly caching bean '" + beanName +
8                           "' to allow for resolving potential circular references");
9               }
10              addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
11          }
12    
13          // Initialize the bean instance.
14          Object exposedObject = bean;
15          try {
16              populateBean(beanName, mbd, instanceWrapper);
17              exposedObject = initializeBean(beanName, exposedObject, mbd);
18          }
19
20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1  /**
2        * Add the given singleton factory for building the specified singleton
3        * if necessary.
4        * <p>To be called for eager registration of singletons, e.g. to be able to
5        * resolve circular references.
6        * @param beanName the name of the bean
7        * @param singletonFactory the factory for the singleton object
8        */
9       protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
10          Assert.notNull(singletonFactory, "Singleton factory must not be null");
11          synchronized (this.singletonObjects) {
12              if (!this.singletonObjects.containsKey(beanName)) {
13                  this.singletonFactories.put(beanName, singletonFactory);
14                  this.earlySingletonObjects.remove(beanName);
15                  this.registeredSingletons.add(beanName);
16              }
17          }
18      }
19
20

可以看到在调用polulateBean触发依赖注入对象的实例化之前,对于单例的bean,调用了addSingletonFactory()方法,我们从这个方法的注释中可以看到,它就是Spring解决单例setter注入的关键所在。也就是说在Spring完整依赖注入之前,已经将这个还未完成依赖注入的bean放到缓存singletonfactoryies里边了。这里出现了几个关键的点:

ObjectFactory

singletonObjects

singletonFactories

earlySingletonObjects

也就是说,我们理清楚了这个点之间的关系,单例setter注入循环依赖也就清楚了。


1
2
3
4
5
6
7
8
9
10
1/** Cache of singleton objects: bean name to bean instance. */
2       private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
3    
4       /** Cache of singleton factories: bean name to ObjectFactory. */
5       private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
6    
7       /** Cache of early singleton objects: bean name to bean instance. */
8       private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
9
10

可以看到几个对象的类型,除了singletonObjects是线程安全的容器,其他两个都是普通的hashMap。根据上面的代码,**我们已经知道singletonFactories存放的是当前正在创建还未完成依赖注入的单例bean和它对应的ObjectFactory。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1// Create bean instance.
2                   if (mbd.isSingleton()) {
3                       sharedInstance = getSingleton(beanName, () -> {
4                           try {
5                               return createBean(beanName, mbd, args);
6                           }
7                           catch (BeansException ex) {
8                               // Explicitly remove instance from singleton cache: It might have been put there
9                               // eagerly by the creation process, to allow for circular reference resolution.
10                              // Also remove any beans that received a temporary reference to the bean.
11                              destroySingleton(beanName);
12                              throw ex;
13                          }
14                      });
15                      bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
16                  }
17
18

这里是创建单例作用域bean的入口,其中有几个关键方法getSingleton(beanName, ObjectFactory)和createBean()方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
1/**
2        * Return the (raw) singleton object registered under the given name,
3        * creating and registering a new one if none registered yet.
4        * @param beanName the name of the bean
5        * @param singletonFactory the ObjectFactory to lazily create the singleton
6        * with, if necessary
7        * @return the registered singleton object
8        */
9       public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
10          Assert.notNull(beanName, "Bean name must not be null");
11          synchronized (this.singletonObjects) {
12              Object singletonObject = this.singletonObjects.get(beanName);
13              if (singletonObject == null) {
14                  if (this.singletonsCurrentlyInDestruction) {
15                      throw new BeanCreationNotAllowedException(beanName,
16                              "Singleton bean creation not allowed while singletons of this factory are in destruction " +
17                              "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
18                  }
19                  if (logger.isDebugEnabled()) {
20                      logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
21                  }
22                  beforeSingletonCreation(beanName);
23                  boolean newSingleton = false;
24                  boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
25                  if (recordSuppressedExceptions) {
26                      this.suppressedExceptions = new LinkedHashSet<>();
27                  }
28                  try {
29                      singletonObject = singletonFactory.getObject();
30                      newSingleton = true;
31                  }
32                  catch (IllegalStateException ex) {
33                      // Has the singleton object implicitly appeared in the meantime ->
34                      // if yes, proceed with it since the exception indicates that state.
35                      singletonObject = this.singletonObjects.get(beanName);
36                      if (singletonObject == null) {
37                          throw ex;
38                      }
39                  }
40                  catch (BeanCreationException ex) {
41                      if (recordSuppressedExceptions) {
42                          for (Exception suppressedException : this.suppressedExceptions) {
43                              ex.addRelatedCause(suppressedException);
44                          }
45                      }
46                      throw ex;
47                  }
48                  finally {
49                      if (recordSuppressedExceptions) {
50                          this.suppressedExceptions = null;
51                      }
52                      afterSingletonCreation(beanName);
53                  }
54                  if (newSingleton) {
55                      addSingleton(beanName, singletonObject);
56                  }
57              }
58              return singletonObject;
59          }
60      }
61
62

这里边看到了在try块中触发了对ObjectFactory的getObject()方法的回调,也就是说调用了createBean()方法。在createBean方法之中完成了对单例的创建,addSingletonFactory()方法的调用以及依赖注入对象的实例化,最后调用了addSingleton()方法,(到这里单例对象已经完成了实例化,是一个完全的对象)将beanName和已经实例化的对象关联放入缓存。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1/**
2        * Add the given singleton object to the singleton cache of this factory.
3        * <p>To be called for eager registration of singletons.
4        * @param beanName the name of the bean
5        * @param singletonObject the singleton object
6        */
7       protected void addSingleton(String beanName, Object singletonObject) {
8           synchronized (this.singletonObjects) {
9               this.singletonObjects.put(beanName, singletonObject);
10              this.singletonFactories.remove(beanName);
11              this.earlySingletonObjects.remove(beanName);
12              this.registeredSingletons.add(beanName);
13          }
14      }
15
16

可以看到addSingleton()方法就是对这几个缓存的处理。也就是说singletonObjects缓存了真正的单例。 也就是说缓存顺序是singletonFactories->singletonObjects,还有earlySingletonObjects的作用未知。在实例化创建对象的入口处首先调用getSingleton()方法。


1
2
3
4
1// Eagerly check singleton cache for manually registered singletons.
2           Object sharedInstance = getSingleton(beanName);
3
4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
1@Override
2       @Nullable
3       public Object   @Override
4       @Nullable
5       public Object getSingleton(String beanName) {
6           return getSingleton(beanName, true);
7       }
8    
9       /**
10       * Return the (raw) singleton object registered under the given name.
11       * <p>Checks already instantiated singletons and also allows for an early
12       * reference to a currently created singleton (resolving a circular reference).
13       * @param beanName the name of the bean to look for
14       * @param allowEarlyReference whether early references should be created or not
15       * @return the registered singleton object, or {@code null} if none found
16       */
17      @Nullable
18      protected Object getSingleton(String beanName, boolean allowEarlyReference) {
19          Object singletonObject = this.singletonObjects.get(beanName);
20          if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
21              synchronized (this.singletonObjects) {
22                  singletonObject = this.earlySingletonObjects.get(beanName);
23                  if (singletonObject == null && allowEarlyReference) {
24                      ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
25                      if (singletonFactory != null) {
26                          singletonObject = singletonFactory.getObject();
27                          this.earlySingletonObjects.put(beanName, singletonObject);
28                          this.singletonFactories.remove(beanName);
29                      }
30                  }
31              }
32          }
33          return singletonObject;
34      }(String beanName) {
35          return getSingleton(beanName, true);
36      }
37    
38      /**
39       * Return the (raw) singleton object registered under the given name.
40       * <p>Checks already instantiated singletons and also allows for an early
41       * reference to a currently created singleton (resolving a circular reference).
42       * @param beanName the name of the bean to look for
43       * @param allowEarlyReference whether early references should be created or not
44       * @return the registered singleton object, or {@code null} if none found
45       */
46      @Nullable
47      protected Object getSingleton(String beanName, boolean allowEarlyReference) {
48          Object singletonObject = this.singletonObjects.get(beanName);
49          if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
50              synchronized (this.singletonObjects) {
51                  singletonObject = this.earlySingletonObjects.get(beanName);
52                  if (singletonObject == null && allowEarlyReference) {
53                      ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
54                      if (singletonFactory != null) {
55                          singletonObject = singletonFactory.getObject();
56                          this.earlySingletonObjects.put(beanName, singletonObject);
57                          this.singletonFactories.remove(beanName);
58                      }
59                  }
60              }
61          }
62          return singletonObject;
63      }
64
65

可以看到getSingleton(beanName)又触发了对getSingleton(beanName, allowEarlyReference)的调用。这个方法就清晰的阐述了三个缓存之间的关系。

1)首先从singletonObjects这个实际存放已经创建好的单例的缓存中获取,如果没有获到,说明当前beanName对应的单例还在创建中或者是首次创建

2)于是到earlySingletonObjects中获取,如果获取到,则返回获取到的对象,如果没有获取到,则从singletonFactories中获取,如果获取到的singletonFactory为null,则表示这是首次创建单例,可以返回了,如果不为null,则表示,当前beanName的单例已经在创建中了,直接调用singletonFactory的getObject()方法获取对象,并将这个对象放入earlySingletonObjects中,如果又出现依赖注入触发了对同一个对象的调用,从earlySingletonObjects取出对象即可,不会再调用singletonFactory.getObject()。问题在于这个getObject()方法怎么获取到还未创建完成的那个单例对象呢?

第一次创建对象的调用getObject()触发了对createBean()的调用,第二次直接返回了创建的对象。怎么做到的?

Spring循环依赖

首次创建bean的时候加入addSingletonFactory()方法是:


1
2
3
1  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
2
3

再次调用是从ObjectFactory方法中获取的,这时候调用的是


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1/**
2        * Obtain a reference for early access to the specified bean,
3        * typically for the purpose of resolving a circular reference.
4        * @param beanName the name of the bean (for error handling purposes)
5        * @param mbd the merged bean definition for the bean
6        * @param bean the raw bean instance
7        * @return the object to expose as bean reference
8        */
9       protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
10          Object exposedObject = bean;
11          if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
12              for (BeanPostProcessor bp : getBeanPostProcessors()) {
13                  if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
14                      SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
15                      exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
16                  }
17              }
18          }
19          return exposedObject;
20      }
21
22

getEarlyBeanReference()直接返回了原来已经创建的对象。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C/C++内存泄漏及检测

2022-1-11 12:36:11

安全经验

BitKey 14.2.0 发布,比特币安全交易系统

2017-5-29 11:12:22

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索