Spring循环依赖
1. 什么是循环依赖
两个或多个对象之间存在相互依赖的情况,形成了一个闭环的依赖关系。
具体来说,在循环依赖中,A依赖于B,B又依赖于C,而C又回到依赖于A。这样的依赖链条形成了一个循环,导致在创建或使用这些对象时出现问题。
示例代码如下:
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中注入C
@Autowired
private C C;
}
@Component
public class C {
// C中注入A
@Autowired
private A a;
}
2. 循环依赖能引发什么问题
循环依赖可能引发以下问题:
-
初始化顺序不确定:循环依赖导致无法确定哪个对象应该先被创建和初始化,从而造成初始化顺序的混乱。这可能导致错误的结果或意外的行为。
-
死锁和无限递归:当循环依赖中的对象在创建过程中相互等待对方完成创建时,可能会导致死锁或无限递归的情况发生。这将使程序无法继续执行,最终导致系统崩溃或进入无限循环。
-
难以理解和维护:循环依赖增加了代码的复杂性和耦合度,使得代码难以理解和维护。在一个循环依赖链中,修改一个类可能会影响到其他所有相关的类,增加了代码的脆弱性。
-
可测试性下降:循环依赖使得各个模块或类之间的责任不清晰,难以进行单独的单元测试或模块拆解。这给软件的测试和调试带来困难,降低了可测试性和可维护性。
为避免循环依赖带来的问题,应尽量避免在设计和实现中出现循环依赖关系。重新考虑系统的架构,采用合适的设计模式、解耦方式和依赖注入机制,可以帮助解决或避免循环依赖带来的问题。
3. Spring是怎么处理循环依赖的
我们先创建如下示例:
public class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
public A() {
System.out.println("---A created success");
}
}
public class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public B() {
System.out.println("---B created success");
}
}
public class ClientCode {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A a = context.getBean("a",A.class);
B b = context.getBean("b",B.class);
}
}
创建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--
1.spring容器默认的单例模式可以解决循环引用,单例默认支持
2.spring容器原型依赖模式scope="prototype"多例模式下不能解决循环引用
-->
<!--depends-on 的意思就是当前这个bean如果要完成,先看depends-on指定的bean是否已经完成了初始化-->
<!--scope="prototype"代表每次都要新建一次对象-->
<bean id="a" class="com.xmc.circular.A" scope="prototype">
<property name="b" ref="b"/>
</bean>
<bean id="b" class="com.xmc.circular.B" scope="prototype">
<property name="a" ref="a"/>
</bean>
</beans>
执行过程中报错如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:274)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
... 17 more
据此我们得到以下结论:
spring容器中原型(
Prototype
)的场景是不支持循环依赖的,抛出BeanCurrentlyInCreationException
异常
其实,Spring容器中单例(singleton
)的场景是支持循环依赖的,Spring使用了三级缓存和提前曝光(early exposure)的机制来处理循环依赖问题
- 第一级缓存(也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
- 第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
- 第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂
DefaultSingletonBeanRegistry
所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map
Spring实例Bean的本质
- 实例化
- 堆内存中申请一块内存空间
- 租赁好房子,自己的家具东西还没有搬家进去
- 初始化属性填充
- 完成属性的各种赋值
- 装修、家电家具进场
3大Map和四大方法,总体相关对象
1.getSingleton:从容器里面获得单例的bean
2.doCreateBean: 没有就创建bean
3.populateBean: 创建完了以后,要填充属性
4.addSingleton: 填充完了以后,再添加到容器进行使用
- 第一层singletonObjects存放的是已经初始化好了的Bean,
- 第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
- 第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean
A/B两对象在三级缓存中的迁移说明
- 1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
- 2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
- 3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态) 然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
4. Spring怎样注入才能不产生循环依赖问题
我们这里直接上结论
依赖情况 | 注入方式 | 能否解决循环依赖 |
---|---|---|
AB相互依赖(循环依赖) | 均采用属性注入 | 不能 |
AB相互依赖(循环依赖) | 均采用set方法注入 | 能 |
AB相互依赖(循环依赖) | 均采用构造器注入 | 不能 |
AB相互依赖(循环依赖) | A中注入B采用属性注入,B中注入A采用set方法注入 | 能 |
AB相互依赖(循环依赖) | A中注入B采用set方法注入,B中注入A采用属性注入 | 能 |
Spring版本
spring-context:5.3.23
属性注入方式验证示例如下:
@Component
public class ComponentA {
@Autowired
private ComponentB componentB;
}
@Component
public class ComponentB {
@Autowired
private ComponentA componentA;
}
// 报错信息如下:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| componentA (field private com.xmc.hello.circular.ComponentB com.xmc.hello.circular.ComponentA.componentB)
↑ ↓
| componentB (field private com.xmc.hello.circular.ComponentA com.xmc.hello.circular.ComponentB.componentA)
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
属性注入方式验证示例如下:
@Component
public class ComponentA {
private ComponentB componentB;
public void setComponentB(ComponentB componentB) {
this.componentB = componentB;
}
}
@Component
public class ComponentB {
private ComponentA componentA;
public void setComponentA(ComponentA componentA) {
this.componentA = componentA;
}
}
// 注入成功
构造器注入方式验证示例如下:
@Component
public class ComponentA {
private ComponentB componentB;
public ComponentA(ComponentB componentB) {
this.componentB = componentB;
}
}
@Component
public class ComponentB {
private ComponentA componentA;
public ComponentB(ComponentA componentA) {
this.componentA = componentA;
}
}
// 报错如下:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| componentA defined in file [E:\ideaworkspaces\myself-ideaworkspaces\spring-boot-test\spring-boot-hello-world\target\classes\com\xmc\hello\circular\ComponentA.class]
↑ ↓
| componentB defined in file [E:\ideaworkspaces\myself-ideaworkspaces\spring-boot-test\spring-boot-hello-world\target\classes\com\xmc\hello\circular\ComponentB.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
A中注入B采用属性注入,B中注入A采用set方法注
@Component
public class ComponentA {
@Autowired
private ComponentB componentB;
}
@Component
public class ComponentB {
private ComponentA componentA;
public void setComponentA(ComponentA componentA) {
this.componentA = componentA;
}
}
// 注入成功
// A中注入B采用set方法注入,B中注入A采用属性注入 和这个示例类似
那么有什么方式可以解决循环依赖了
set
方法注入- 使用
@Lazy
注解 - 可以采用
cn.hutool.extra.spring#SpringUtil.getBean()
方法 - 当然业务重新设计,解除循环依赖更好
5. 总结
Spring是如何解决的循环依赖?
- Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化
- 每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个
- 当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程,不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建
- 既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成
Spring解决循环依赖依靠的是Bean的中间态这个概念,而这个中间态指的是已经实例化但还没初始化的状态,也就是
半成品。 实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
Spring为了解决单例的循环依赖问题,使用了三级缓存其中
- 一级缓存为单例池(
singletonObjects
) - 二级缓存为提前曝光对象(
earlySingletonObjects
) - 三级缓存为提前曝光对象工厂(
singletonFactories
)。
假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。
Spring解决循环依赖的整个流程图
Debug的步骤---->Spring解决循环依赖过程
- 1 调用
doGetBean()
方法,想要获取beanA
,于是调用getSingleton()
方法从缓存中查找beanA
- 2 在
getSingleton()
方法中,从一级缓存中查找,没有,返回null- 3
doGetBean()
方法中获取到的beanA
为null,于是走对应的处理逻辑,调用getSingleton()
的重载方法(参数为ObjectFactory
的)- 4 在
getSingleton()
方法中,先将beanA_name
添加到一个集合中,用于标记该bean
正在创建中。然后回调匿名内部类的creatBean
方法- 5 进入
AbstractAutowireCapableBeanFactory#doCreateBean
,先反射调用构造器创建出beanA
的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中- 6 对
beanA
进行属性填充,此时检测到beanA
依赖于beanB
,于是开始查找beanB
- 7 调用
doGetBean()
方法,和上面beanA
的过程一样,到缓存中查找beanB,没有则创建,然后给beanB
填充属性- 8 此时
beanB
依赖于beanA
,调用getsingleton()
获取beanA
,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA
的创建工厂,通过创建工厂获取到singletonObject
,此时这个singletonObject
指向的就是上面在doCreateBean()
方法中实例化的beanA
- 9 这样
beanB
就获取到了beanA
的依赖,于是beanB
顺利完成实例化,并将beanA
从三级缓存移动到二级缓存中- 10 随后
beanA
继续他的属性填充工作,此时也获取到了beanB
,beanA
也随之完成了创建,回到getsingleton()
方法中继续向下执行,将beanA
从二级缓存移动到一级缓存中