spring如何快速穩定解決循環依賴問題
循環依賴其實就是循環引用,很多地方都說需要兩個或則兩個以上的bean互相持有對方最終形成閉環才是循環依賴,比如A依賴於B,B依賴於C,C又依賴於A。其實一個bean持有自己類型的屬性也會產生循環依賴。
setter singleton循環依賴
使用
SingleSetterBeanA依賴SingleSetterBeanB,SingleSetterBeanB依賴SingleSetterBeanA。
@Data public class SingleSetterBeanA { @Autowired private SingleSetterBeanB singleSetterBeanB; }
@Data public class SingleSetterBeanB { @Autowired private SingleSetterBeanA singleSetterBeanA; }
源碼分析
spring是通過三級緩存來解決循環依賴的,那麼三級緩存是怎麼工作的呢?
三級緩存對應org.springframework.beans.factory.support.DefaultSingletonBeanRegistry類的三個屬性:
/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一級緩存 /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 二級緩存 /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三級緩存
對於setter註入造成的依賴是通過Spring容器提前暴露剛完成實例化但未完成初始化的bean來完成的,而且隻能解決單例作用域的bean循環依賴。通過提前暴露一個單例工廠方法,從而使其他bean能引用到該bean,關鍵源碼如下所示:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// 處理循環依賴,實例化後放入三級緩存 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實例化後放入三級緩存中:
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); // 三級緩存 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
放入三級緩存中的是ObjectFactory類型的lambda表達式:
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創建代理對象 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
構造器參數循環依賴
通過構造器註入構成的循環依賴,此依賴是無法解決的,隻能拋出BeanCurrentlyInCreationException異常表示循環依賴。
使用
@Data public class SingleConstrutorBeanA { public SingleConstrutorBeanA(SingleConstrutorBeanB singleConstrutorBeanB) { } }
@Data public class SingleConstrutorBeanB { public SingleConstrutorBeanB(SingleConstrutorBeanA singleConstrutorBeanA) { } }
上面的代碼運行時會拋出如下異常:
… …
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容器會將每一個正在創建的Bean標識符放在一個“當前創建Bean池”中,Bean標識符在創建過程中將一直保持在這個池中,因此如果在創建Bean過程中發現自己已經在“當前創建Bean池”裡時將拋出BeanCurrentlyInCreationException異常表示循環依賴;而對於創建完畢的Bean將從“當前創建Bean池”中清除掉。
protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
@Lazy打破循環依賴
在上面的例子中隻需要在SingleConstrutorBeanA或者SingleConstrutorBeanB的構造方法上面加上@Lazy註解,就會發現不會拋出異常瞭,這又是為什麼呢?
下面假設在SingleConstrutorBeanA的構造方法上面加瞭@Lazy註解,在構造B時,發現參數A時被@Lazy註解修飾時,那麼就不會調用getBean來獲取對象,而是創建瞭一個代理對象,所以不會構成真正的循環依賴,不會拋出BeanCurrentlyInCreationException異常。
/** * 處理懶加載對象 * 懶加載返回的又是一個代理對象,不會真正的調用getBean,所以如果構造方法依賴中有循環依賴,那麼不會報錯 * @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) { // 調用beanFactory.getBean(beanName)從容器中獲取依賴對象 result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result;
setter prototype循環依賴
對於prototype作用域bean,Spring容器無法完成依賴註入,因為Spring容器不進行緩存”prototype”作用域的bean,因此無法提前暴露一個創建中的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); // 此時必須要獲取Spring管理的實例,因為現在scope="prototype" 隻有請求獲取的時候才會實例化對象 }
運行結果如下:
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
... ... // 判斷是否在當前創建Bean池中 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } ... ...
異常就是在上面的代碼中拋出來的,那麼beanName是什麼時候添加至當前創建Bean池中的呢?
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. // prototype類型的bean的實例化 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容器不會對prototype類型的bean進行緩存,因此無法提前利用三級緩存暴露一個代理對象。
循環依賴開關
可以通過allowCircularReferences來禁止循環依賴,這樣的話,singleton bean的setter循環依賴也會報錯。
applicationContext.setAllowCircularReferences(false);
二級緩存可行?
緩存 | 說明 |
---|---|
singletonObjects | 第一級緩存,存放可用的成品Bean。 |
earlySingletonObjects | 第二級緩存,存放半成品的Bean,半成品的Bean是已創建對象,但是未註入屬性和初始化,用以解決循環依賴。 |
singletonFactories | 第三級緩存,存的是Bean工廠對象,用來生成半成品的Bean並放入到二級緩存中,用以解決循環依賴。 |
理論上二級緩存時可行的,隻需要將三級緩存中BeanFactory創建的對象提前放入二級緩存中,這樣三級緩存就可以移除瞭。
那麼spring中為什麼還要使用三級緩存呢?如果要使用二級緩存解決循環依賴,意味著所有Bean在實例化後就要完成AOP代理,這樣違背瞭Spring設計的原則,Spring在設計之初就是通過AnnotationAwareAspectJAutoProxyCreator這個後置處理器來在Bean生命周期的最後一步來完成AOP代理,而不是在實例化後就立馬進行AOP代理。
到此這篇關於spring如何快速穩定解決循環依賴問題的文章就介紹到這瞭,更多相關spring循環依賴內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- spring如何解決循環依賴問題詳解
- Spring整合Mybatis 掃描註解創建Bean報錯的解決方案
- 關於Spring Bean實例過程中使用反射和遞歸處理的Bean屬性填充問題
- Spring BeanPostProcessor源碼示例解析
- Spring註解方式無法掃描Service註解的解決