优质博文:IT-BLOG-CN
一、问题现状
在
Spring
框架中,循环依赖
Circular Dependency
是指两个或多个
Bean
相互依赖,形成一个循环引用。例如,
Bean A
依赖于
Bean B
,而
Bean B
又依赖于
Bean A
。这种情况可能会导致
Spring
容器在创建
Bean
时出现问题。如下:
publicclassA{privatefinalB b;publicA(B b){this.b = b;}}publicclassB{privatefinalA a;publicB(A a){this.a = a;}}
启动时会出现如下错误:
org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean withname'A':Requested bean is currently in creation:Is there an unresolvable circular reference?
二、解决循环依赖的方法
【1】构造器注入:构造器注入不支持循环依赖,因为在
Spring
容器尝试创建
Bean
时,它需要立即解析所有的构造函数参数,这会导致循环依赖问题。因此,避免使用构造器注入来解决循环依赖。
@Lazy
注解可以延迟
Bean
的初始化,使得
Spring
在构造器注入时也能解决循环依赖问题。具体来说,
@Lazy
会告诉
Spring
在第一次使用
Bean
时才进行初始化,而不是在容器启动时立即初始化。
importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Lazy;importorg.springframework.stereotype.Component;@ComponentpublicclassA{privatefinalB b;@AutowiredpublicA(@LazyB b){this.b = b;}}@ComponentpublicclassB{privatefinalA a;@AutowiredpublicB(@LazyA a){this.a = a;}}
**【2】
Setter
注入:**
Setter
注入可以解决循环依赖问题,因为
Spring
容器可以首先创建
Bean
的实例,然后再注入依赖。
publicclassA{privateB b;publicvoidsetB(B b){this.b = b;}}publicclassB{privateA a;publicvoidsetA(A a){this.a = a;}}
**【3】
@Autowired
注解:** 使用
@Autowired
注解进行
Setter
注入或者字段注入,也可以解决循环依赖问题。
publicclassA{@AutowiredprivateB b;}publicclassB{@AutowiredprivateA a;}
**【4】
@Lazy
注解:** 使用
@Lazy
注解可以延迟
Bean
的初始化,从而解决循环依赖问题。
publicclassA{@Autowired@LazyprivateB b;}publicclassB{@Autowired@LazyprivateA a;}
**【5】使用
ObjectFactory
或
Provider
:** 使用
ObjectFactory
或
Provider
可以在需要时才获取
Bean
实例,从而解决循环依赖问题。
publicclassA{@AutowiredprivateObjectFactory<B> bFactory;publicvoidsomeMethod(){B b = bFactory.getObject();// 使用B}}publicclassB{@AutowiredprivateObjectFactory<A> aFactory;publicvoidsomeMethod(){A a = aFactory.getObject();// 使用A}}
**【6】配置
allow-circular-references: true
:** 用于控制是否允许
Bean
之间的循环依赖。
true
:允许
Bean
之间存在循环依赖。
Spring
容器会尝试通过创建
Bean
的代理对象来解决循环依赖问题。这是默认行为。但从设计和架构的角度来看,尽量避免循环依赖是更好的做法。
spring:main:allow-circular-references:true
三、三级缓存的组成
首先,我们要知道
Spring
在创建
Bean
的时候默认是按照自然排序进行创建的,所以第一步
Spring
会去创建
A
。
Spring
创建
Bean
的过程中分为三步:
1、实例化: 对应方法
AbstractAutowireCapableBeanFactory
中的
createBeanInstance
方法,简单理解就是
new
了一个对象。
2、属性注入: 对应方法
AbstractAutowireCapableBeanFactory
的
populateBean
方法,为实例化中
new
出来的对象填充属性。
3、初始化: 对应方法
AbstractAutowireCapableBeanFactory
的
initializeBean
,执行
aware
接口中的方法,初始化方法,完成
AOP
代理。
Spring
是如何解决循环依赖问题的:三级缓存
三级缓存的组成
**一级缓存
singletonObjects
:** 存储已经完全初始化的单例
Bean
。类型
ConcurrentHashMap<String, Object>
**二级缓存
earlySingletonObjects
:** 多了一个
early
,表示缓存的是早期的
bean
对象。早期是什么意思?表示
Bean
的生命周期还没走完就把这个
Bean
放入
earlySingletonObjects
,通常是为了避免循环依赖。类型
ConcurrentHashMap<String, Object>
**三级缓存
singletonFactories
:** 存储创建
Bean
的工厂
ObjectFactory
,用于解决循环依赖。类型:
ConcurrentHashMap<String, ObjectFactory<?>>
/** Cache of singleton objects: bean name --> bean instance */privatefinalMap<String,Object> singletonObjects =newConcurrentHashMap<String,Object>(256);/** Cache of singleton factories: bean name --> ObjectFactory */privatefinalMap<String,ObjectFactory<?>> singletonFactories =newHashMap<String,ObjectFactory<?>>(16);/** Cache of early singleton objects: bean name --> bean instance */privatefinalMap<String,Object> earlySingletonObjects =newHashMap<String,Object>(16);
四、三级缓存的工作原理
**创建
Bean
实例:** 当
Spring
容器创建一个
Bean
时,首先会尝试从一级缓存
singletonObjects
中获取该
Bean
。如果获取不到,再尝试从二级缓存
earlySingletonObjects
中获取。如果仍然获取不到,再尝试从三级缓存
singletonFactories
中获取。
protectedObjectgetSingleton(String beanName,boolean allowEarlyReference){Object singletonObject =this.singletonObjects.get(beanName);if(singletonObject ==null&&isSingletonCurrentlyInCreation(beanName)){synchronized(this.singletonObjects){
singletonObject =this.earlySingletonObjects.get(beanName);if(singletonObject ==null&& allowEarlyReference){ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);if(singletonFactory !=null){
singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return(singletonObject != NULL_OBJECT ? singletonObject :null);}
**提前曝光
Bean
:** 在创建
Bean
的过程中,如果检测到循环依赖,
Spring
会提前将该
Bean
的一个早期引用(通常是通过
ObjectFactory
创建的代理对象)放入三级缓存
singletonFactories
中。
解决循环依赖: 当另一个
Bean
需要依赖这个尚未完全初始化的
Bean
时,会从三级缓存
singletonFactories
中获取该
Bean
的早期引用,并将其放入二级缓存
earlySingletonObjects
中。
完成初始化: 一旦
Bean
完全初始化完成,
Spring
会将其从二级缓存
earlySingletonObjects
中移除,并放入一级缓存
singletonObjects
中。
案例:如上有两个类
A
和
B
,它们通过构造器注入互相依赖:
**【1】创建
A
的实例:**
A
依赖
B
,但
B
尚未创建。
Spring
会将
A
的早期引用(通常是一个代理对象)放入三级缓存
singletonFactories
中。
**【2】创建
B
的实例:**
B
依赖
A
,
Spring
会从三级缓存
singletonFactories
中获取
A
的早期引用,并将其放入二级缓存
earlySingletonObjects
中。通过早期引用可知,
B
注入的是
A
的引用,所以最终拿到的是一个完整的
A
对象。
**【3】完成
B
的初始化:**
B
完全初始化后,放入一级缓存
singletonObjects
中。
**【4】完成
A
的初始化:**
A
获取到
B
的完全初始化的实例后,完成自身初始化,并放入一级缓存
singletonObjects
中。
五、spring 循环依赖为什么使用三级缓存而不是二级缓存
【1】代理对象的创建: 在某些情况下,
Spring
需要为
bean
创建代理对象(例如,使用
AOP
时)。代理对象的创建通常在
bean
初始化的后期阶段进行。
如果只使用二级缓存,意味着所有
Bean
在实例化后就要完成
AOP
代理,在某些情况下,
Spring
可能无法正确地创建代理对象,因为代理对象的创建依赖于完整的
bean
初始化过程,这样违背了
Spring
设计的原则,
Spring
在设计之初就是通过
AnnotationAwareAspectJAutoProxyCreator
这个后置处理器来在Bean生命周期的最后一步来完成
AOP
代理,而不是在实例化后就立马进行
AOP
代理。
三级缓存中的对象工厂可以确保在需要时创建代理对象,并将其放入二级缓存,从而确保代理对象可以在循环依赖中正确地被引用。
【2】延迟创建早期引用: 三级缓存中的对象工厂允许
Spring
在需要时延迟创建早期引用,而不是立即创建。这种延迟创建机制可以确保在某些特殊情况下,
bean
可以在完全初始化之前被引用。
通过这种方式,
Spring
可以更灵活地处理各种复杂的依赖关系和代理对象的创建。
版权归原作者 程序猿进阶 所有, 如有侵权,请联系我们删除。