spring如何解決循環(huán)依賴問題詳解
循環(huán)依賴其實(shí)就是循環(huán)引用,很多地方都說需要兩個(gè)或則兩個(gè)以上的bean互相持有對(duì)方最終形成閉環(huán)才是循環(huán)依賴,比如A依賴于B,B依賴于C,C又依賴于A。其實(shí)一個(gè)bean持有自己類型的屬性也會(huì)產(chǎn)生循環(huán)依賴。
setter singleton循環(huán)依賴
使用
SingleSetterBeanA依賴SingleSetterBeanB,SingleSetterBeanB依賴SingleSetterBeanA。
@Data public class SingleSetterBeanA { @Autowired private SingleSetterBeanB singleSetterBeanB; }
@Data public class SingleSetterBeanB { @Autowired private SingleSetterBeanA singleSetterBeanA; }
源碼分析
spring是通過三級(jí)緩存來解決循環(huán)依賴的,那么三級(jí)緩存是怎么工作的呢?
三級(jí)緩存對(duì)應(yīng)org.springframework.beans.factory.support.DefaultSingletonBeanRegistry類的三個(gè)屬性:
/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一級(jí)緩存 /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 二級(jí)緩存 /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三級(jí)緩存
對(duì)于setter注入造成的依賴是通過Spring容器提前暴露剛完成實(shí)例化但未完成初始化的bean來完成的,而且只能解決單例作用域的bean循環(huán)依賴。通過提前暴露一個(gè)單例工廠方法,從而使其他bean能引用到該bean,關(guān)鍵源碼如下所示:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// 處理循環(huán)依賴,實(shí)例化后放入三級(jí)緩存 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"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
bean實(shí)例化后放入三級(jí)緩存中:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); // 三級(jí)緩存 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
放入三級(jí)緩存中的是ObjectFactory類型的lambda表達(dá)式:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; /** * @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference(java.lang.Object, java.lang.String) */ // 使用AbstractAutoProxyCreator#getEarlyBeanReference創(chuàng)建代理對(duì)象 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
構(gòu)造器參數(shù)循環(huán)依賴
通過構(gòu)造器注入構(gòu)成的循環(huán)依賴,此依賴是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴。
使用
@Data public class SingleConstrutorBeanA { public SingleConstrutorBeanA(SingleConstrutorBeanB singleConstrutorBeanB) { } }
@Data public class SingleConstrutorBeanB { public SingleConstrutorBeanB(SingleConstrutorBeanA singleConstrutorBeanA) { } }
上面的代碼運(yùn)行時(shí)會(huì)拋出如下異常:
... ... Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'singleConstrutorBeanB': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singleConstrutorBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:805) at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:228) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1403) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1245) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:579) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:538) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:329) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:892) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:796) ... 76 more Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singleConstrutorBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:892) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:796) ... 90 more
源碼分析
Spring容器會(huì)將每一個(gè)正在創(chuàng)建的Bean標(biāo)識(shí)符放在一個(gè)“當(dāng)前創(chuàng)建Bean池”中,Bean標(biāo)識(shí)符在創(chuàng)建過程中將一直保持在這個(gè)池中,因此如果在創(chuàng)建Bean過程中發(fā)現(xiàn)自己已經(jīng)在“當(dāng)前創(chuàng)建Bean池”里時(shí)將拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴;而對(duì)于創(chuàng)建完畢的Bean將從“當(dāng)前創(chuàng)建Bean池”中清除掉。
protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
@Lazy打破循環(huán)依賴
在上面的例子中只需要在SingleConstrutorBeanA或者SingleConstrutorBeanB的構(gòu)造方法上面加上@Lazy注解,就會(huì)發(fā)現(xiàn)不會(huì)拋出異常了,這又是為什么呢?
下面假設(shè)在SingleConstrutorBeanA的構(gòu)造方法上面加了@Lazy注解,在構(gòu)造B時(shí),發(fā)現(xiàn)參數(shù)A時(shí)被@Lazy注解修飾時(shí),那么就不會(huì)調(diào)用getBean來獲取對(duì)象,而是創(chuàng)建了一個(gè)代理對(duì)象,所以不會(huì)構(gòu)成真正的循環(huán)依賴,不會(huì)拋出BeanCurrentlyInCreationException異常。
/** * 處理懶加載對(duì)象 * 懶加載返回的又是一個(gè)代理對(duì)象,不會(huì)真正的調(diào)用getBean,所以如果構(gòu)造方法依賴中有循環(huán)依賴,那么不會(huì)報(bào)錯(cuò) * @see org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String) */ Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { // 調(diào)用beanFactory.getBean(beanName)從容器中獲取依賴對(duì)象 result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result;
setter prototype循環(huán)依賴
對(duì)于prototype作用域bean,Spring容器無法完成依賴注入,因?yàn)镾pring容器不進(jìn)行緩存"prototype"作用域的bean,因此無法提前暴露一個(gè)創(chuàng)建中的bean。
使用
@Data @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBeanA { @Autowired private PrototypeBeanB prototypeBeanB; }
@Data @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBeanB { @Autowired private PrototypeBeanA prototypeBeanA; }
@Test public void test3() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.register(PrototypeBeanA.class); applicationContext.register(PrototypeBeanB.class); applicationContext.refresh(); applicationContext.getBean(PrototypeBeanA.class); // 此時(shí)必須要獲取Spring管理的實(shí)例,因?yàn)楝F(xiàn)在scope="prototype" 只有請(qǐng)求獲取的時(shí)候才會(huì)實(shí)例化對(duì)象 }
運(yùn)行結(jié)果如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:269) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1322) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1240) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:668) ... 89 more
源碼分析
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
... ... // 判斷是否在當(dāng)前創(chuàng)建Bean池中 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } ... ...
異常就是在上面的代碼中拋出來的,那么beanName是什么時(shí)候添加至當(dāng)前創(chuàng)建Bean池中的呢?
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. // prototype類型的bean的實(shí)例化 Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); }
org.springframework.beans.factory.support.AbstractBeanFactory#beforePrototypeCreation
protected void beforePrototypeCreation(String beanName) { // ThreadLocal Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal == null) { this.prototypesCurrentlyInCreation.set(beanName); } else if (curVal instanceof String) { Set<String> beanNameSet = new HashSet<>(2); beanNameSet.add((String) curVal); beanNameSet.add(beanName); this.prototypesCurrentlyInCreation.set(beanNameSet); } else { Set<String> beanNameSet = (Set<String>) curVal; beanNameSet.add(beanName); } }
其根本原因就是Spring容器不會(huì)對(duì)prototype類型的bean進(jìn)行緩存,因此無法提前利用三級(jí)緩存暴露一個(gè)代理對(duì)象。
循環(huán)依賴開關(guān)
可以通過allowCircularReferences來禁止循環(huán)依賴,這樣的話,singleton bean的setter循環(huán)依賴也會(huì)報(bào)錯(cuò)。
二級(jí)緩存可行?
緩存 | 說明 |
---|---|
singletonObjects | 第一級(jí)緩存,存放可用的成品Bean。 |
earlySingletonObjects | 第二級(jí)緩存,存放半成品的Bean,半成品的Bean是已創(chuàng)建對(duì)象,但是未注入屬性和初始化,用以解決循環(huán)依賴。 |
singletonFactories | 第三級(jí)緩存,存的是Bean工廠對(duì)象,用來生成半成品的Bean并放入到二級(jí)緩存中,用以解決循環(huán)依賴。 |
理論上二級(jí)緩存時(shí)可行的,只需要將三級(jí)緩存中BeanFactory創(chuàng)建的對(duì)象提前放入二級(jí)緩存中,這樣三級(jí)緩存就可以移除了。
那么spring中為什么還要使用三級(jí)緩存呢?如果要使用二級(jí)緩存解決循環(huán)依賴,意味著所有Bean在實(shí)例化后就要完成AOP代理,這樣違背了Spring設(shè)計(jì)的原則,Spring在設(shè)計(jì)之初就是通過AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來在Bean生命周期的最后一步來完成AOP代理,而不是在實(shí)例化后就立馬進(jìn)行AOP代理。
前言
到此這篇關(guān)于spring如何解決循環(huán)依賴問題的文章就介紹到這了,更多相關(guān)spring循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot @ComponentScan注解原理解析
這篇文章主要介紹了springboot @ComponentScan注解原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Maven?繼承父工程時(shí)的relativePath標(biāo)簽詳細(xì)解析
這篇文章主要介紹了Maven?繼承父工程時(shí)的relativePath標(biāo)簽解析,通過本文學(xué)習(xí)你需要注意子模塊想要用父模塊pom中的版本,請(qǐng)注意配置relativePath屬性,需要的朋友可以參考下2022-12-12Kotlin + Retrofit + RxJava簡(jiǎn)單封裝使用詳解
這篇文章主要介紹了Kotlin + Retrofit + RxJava簡(jiǎn)單封裝使用詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07Mybatis反向工程出現(xiàn)BigDecimal類型問題及解決
這篇文章主要介紹了Mybatis反向工程出現(xiàn)BigDecimal類型問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09springboot使用TaskScheduler實(shí)現(xiàn)動(dòng)態(tài)增刪啟停定時(shí)任務(wù)方式
這篇文章主要介紹了springboot使用TaskScheduler實(shí)現(xiàn)動(dòng)態(tài)增刪啟停定時(shí)任務(wù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08JavaWeb中struts2實(shí)現(xiàn)文件上傳下載功能實(shí)例解析
這篇文章主要介紹了JavaWeb中struts2文件上傳下載功能的實(shí)現(xiàn),在Web應(yīng)用系統(tǒng)開發(fā)中,文件上傳和下載功能是非常常用的功能,需要的朋友可以參考下2016-05-05使用JWT創(chuàng)建解析令牌及RSA非對(duì)稱加密詳解
這篇文章主要介紹了JWT創(chuàng)建解析令牌及RSA非對(duì)稱加密詳解,JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌,一種情況是webapi,類似之前的阿里云播放憑證的功能,另一種情況是多web服務(wù)器下實(shí)現(xiàn)無狀態(tài)分布式身份驗(yàn)證,需要的朋友可以參考下2023-11-11