- Bean的循坏依赖问题
- 单例模式+set注入
- 多例模式+set注入(报异常)
- 单例模式+构造注入(报异常)
- Spring解决循坏依赖的机理(源码分析)
九、Bean的循坏依赖问题
9.1 什么是Bean的循坏依赖
Bean
的循环依赖,就是指 A 对象中有 B 属性,B 对象中有 A 属性。- 即我依赖你,你也依赖我。
比如:丈夫类
Husband
,妻子类Wife
,Husband
中有Wife
的引用,Wife
中有Husband
的引用。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
30package com.f.spring6.bean;
/**
* @author fzy
* @date 2024/1/21 16:00
*/
public class Husband {
private String name;
private Wife wife;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}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
30package com.f.spring6.bean;
/**
* @author fzy
* @date 2024/1/21 16:00
*/
public class Wife {
private String name;
private Husband husband;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
9.2 单例模式+set注入
1 |
|
1 |
|
分析 “单例模式+
set
注入” 情况下的循坏依赖:- 在
set
+singleton
模式下 Spring 对Bean
的管理主要分为两个阶段:- 第一个阶段:在 Spring 容器加载的时候,实例化
Bean
,只要其中任意一个Bean
实例化之后,马上进行“曝光”,不等属性赋值就 “曝光”。- 提前曝光,不等属性赋值就可以使用,因为单例下就只会创建该类型的对象一个,早曝光和晚曝光一样,都是这个对象。
- 第二个阶段:Bean “曝光” 之后,再进行属性的赋值,调用
set
方法。
- 第一个阶段:在 Spring 容器加载的时候,实例化
- 解决单例模式 +
set
注入情况下的循环依赖的核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。
注意:只有在
scope
是singleton
的情况下,Bean
才会采取提前 “曝光” 的措施。prototype
下不会进行提前曝光,如果多例模式下多个对象进行提前曝光,则 Spring 在使用对象的时候,不知道要使用哪个对象。- 在
9.3 多例模式+set注入(报异常)
1 |
|
1 |
|
分析 “多例模式+
set
注入” 情况下的循坏依赖:当循环依赖的两个
bean
的scope
都是prototype
的时候,即在prototype
+set
注入下的循环依赖,会存在问题,会出现异常BeanCurrentlyInCreationException
(当前的Bean
正处于创建中异常)。1
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'husband' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'wife' while setting bean property 'wife'
原因在于:
- 因为都是多例模式,所以每次需要对象的时候都会
new
新的对象出来,A 对象中需要 B 对象,会new
一个新的 B 对象,B 对象中需要 A 对象,会new
一个新的 A 对象,新的 A 对象中需要 B 对象,会再new
一个新的 B 对象……最终导致对象永远创建不完。- 如果其中任意一个是
singleton
的,就不会出现异常(因为单例模式下的bean
实例化完成以后就会进行“曝光”,“曝光”后就可以停止死循环依赖)。
- 如果其中任意一个是
singleton
表示在解析配置文件时就会被创建,当该singleton
Bean
中的属性需要prototype
Bean
时,可以马上创建一个prototype
对象赋值给singleton
,而prototype
对象所需的singleton
只有唯一的一个,从而可以让依赖停止下来,所以没有问题。
9.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
27package com.f.spring6.bean2;
/**
* @author fzy
* @date 2024/1/21 16:00
*/
public class Husband {
private String name;
private Wife wife;
public Husband(String name, Wife wife) {
this.name = name;
this.wife = wife;
}
public String getName() {
return name;
}
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}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
27package com.f.spring6.bean2;
/**
* @author fzy
* @date 2024/1/21 16:00
*/
public class Wife {
private String name;
private Husband husband;
public Wife(String name, Husband husband) {
this.name = name;
this.husband = husband;
}
public String getName() {
return name;
}
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--单例模式+构造注入下的循坏依赖-->
<bean id="husband" class="com.f.spring6.bean2.Husband">
<constructor-arg name="name" value="jack"/>
<constructor-arg name="wife" ref="wife"/>
</bean>
<bean id="wife" class="com.f.spring6.bean2.Wife">
<constructor-arg name="name" value="lucy"/>
<constructor-arg name="husband" ref="husband"/>
</bean>
</beans>1
2
3
4
5
6
7
8
public void testCD2() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
Husband husband = applicationContext.getBean("husband", Husband.class);
System.out.println(husband);
Wife wife = applicationContext.getBean("wife", Wife.class);
System.out.println(wife);
}会报异常:
1
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'husband' defined in class path resource [spring2.xml]: Cannot resolve reference to bean 'wife' while setting constructor argument
基于构造注入方式下的循环依赖是会报异常的,因为创建对象需要另一个对象作为属性,导致无法完成对象的创建。
例如:当创建 A 对象的时候,需要 B 对象,会马上进行 B 对象的创建,但是创建 B 对象又需要 A 对象,可是这个时候 A 对象还在创建中,导致 B 对象也无法完成创建,最终两个对象的创建都无法进行,使得创建对象和依赖注入失败。
- 基于构造注入的方式下产生的循环依赖问题是无法解决的,因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程不能分离。
9.5 Spring解决循坏依赖的机理(源码分析)
Spring 可以解决
set
+singleton
模式下循环依赖:根本原因在于:这种方式可以做到将 “实例化Bean” 和 “给Bean属性赋值” 这两个动作分开去完成。
- 实例化
Bean
的时候:调用无参数构造方法来完成,此时可以先不给属性赋值,可以提前将该Bean
对象“曝光”给外界。 - 给
Bean
属性赋值的时候:调用setter
方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,
Bean
都是单例的,我们可以先把所有的单例Bean
实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean
全部实例化完成之后,我们再慢慢的调用setter
方法给单例Bean
的属性赋值。这样就解决了循环依赖的问题。- 实例化
我们来看 Spring 的源码:
Spring 容器启动后,会执行
AbstractAutowireCapableBeanFactory
中的doCreateBean
方法进行Bean
的创建:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 实例化Bean,但还没有给Bean的属性赋值
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
......1
2
3
4
5
6
7
8
9
10
11
12
13
14......
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 缓存单例Bean
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
......1
2
3
4
5
6
7
8
9
10
11protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 将单例Bean对应的单例工厂对象存储到三级缓存中
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}这里对
Bean
进行缓存,缓存的是创建Bean
的工厂对象,从而对Bean
进行曝光,使得需要使用该Bean
的对象可以获取该Bean
。addSingletonFactory
为DefaultSingletonBeanRegistry
(默认单例Bean
注册) 类中的方法。在
DefaultSingletonBeanRegistry
类中有三个比较重要的Map
集合,即缓存Bean
的三个缓存:private final Map<String, Object> singletonObjects
:一级缓存private final Map<String, Object> earlySingletonObjects
:二级缓存private final Map<String, ObjectFactory<?>> singletonFactories
:三级缓存
Map
集合的key
存储的是bean
的name
(bean id
):- 一级缓存存储的是:单例
Bean
对象,完整的单例Bean
对象。也就是说这个缓存中的Bean
对象的属性都已经赋值了,是一个完整的Bean
对象。 - 二级缓存存储的是:早期的单例
Bean
对象。这个缓存中的单例Bean
对象的属性没有赋值,只是一个早期的实例对象。 - 三级缓存存储的是:单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例
Bean
对象都会对应一个单例工厂对象。这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock // 从一级缓存中获取单例Bean对象 singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 从二级缓存中获取单例Bean对象 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 从三级缓存中获取单例Bean对象 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
从源码中可以看到,Spring 会先从一级缓存中获取
Bean
;如果获取不到,则从二级缓存中获取
Bean
;如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的
ObjectFactory
对象,通过ObjectFactory
对象获取Bean
实例,这样就解决了循环依赖的问题。单例
Bean
对象的创建和赋值过程分开,提前进行Bean
曝光,从而解决循环依赖问题。
Spring 只能解决
setter
方法注入的单例bean
之间的循环依赖。ClassA
依赖ClassB
,ClassB
又依赖ClassA
,形成循环依赖。Spring 在创建
ClassA
对象后,不需要等待给属性赋值,就直接将其曝光到bean
缓存当中。在解析ClassA
的属性时,发现其依赖于ClassB
,就去获取ClassB
,当解析ClassB
的属性时,又发现其依赖于ClassA
,但此时ClassA
已经被提前曝光,加入了bean
的缓存中,所以直接从缓存中获取ClassA
实例即可,从而解决循环依赖问题。