1. 程式人生 > 實用技巧 >spring學習總結010 --- 迴圈依賴

spring學習總結010 --- 迴圈依賴

先說一下迴圈依賴常見問題的個人理解:

1、spring只能解決屬性或者setter注入場景下的迴圈依賴?? ------ 我理解不是,在構造器注入和屬性注入混合場景下也是能夠解決的

2、spring解決迴圈依賴採用了三級快取,之所以用三級快取是為了提升效率?? ------- 我理解三級快取和二級快取效率相差無幾,只不過為了解決AOP場景下生命週期問題

什麼是迴圈依賴

A依賴B的同時,B也依賴A,這就是迴圈依賴,用程式碼表示如下:

public class BeanB {

    @Autowired
    private BeanA beanA;
}
public class BeanA {

    @Autowired
    
private BeanB beanB; }

spring能解決哪種場景下的迴圈依賴

1、A和B均通過構造器依賴注入 ----- spring無法解決

2、A和B均通過屬性注入 ----- spring可以解決

3、A通過屬性注入B,B通過構造器注入A ----- spring可以解決

4、A通過構造器注入B,B通過屬性注入A ----- spring無法解決

spring如何解決迴圈依賴

spring中解決迴圈依賴bean的主要建立過程如下:

三級快取原始碼中的位置及快取的內容如下:

以A和B均屬性注入產生迴圈依賴,說明spring如何解決迴圈依賴:

建立bean都是從AbstractBeanFactory的getBean方法入口,然後呼叫getSingleton獲取bean,當然建立BeanA的時候,getSingleton返回空;getSingleton方法原始碼:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從一級快取中獲取bean
    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) { // 命中三級快取, 將bean例項從三級快取移動到二級快取 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }

然後進入另一個getSingleton方法,該方法的第二個引數是lamba表示式,裡面呼叫了createBean方法,返回單例工廠例項:

然後在doCreteBean方法中呼叫前面提到的三個方法,呼叫createBeanInstance建立例項,然後將該例項轉換成例項工廠物件,存放到三級快取

接下來在populateBean方法中填充屬性,發現bean定義中BeanA依賴BeanB,然後呼叫getBean方法獲取BeanB獲取BeanB例項方法和獲取BeanA一致,直到BeanB處理屬性填充;

BeanB填充屬性BeanA時,先從快取中獲取,這裡命中的是三級快取;然後完成了BeanB的屬性填充、初始化等操作,得到了完整的BeanB例項;

接下來繼續執行BeanA的初始化,直到建立完成。

前面說到命中三級快取返回的物件是不完整的,那麼是否意味著BeanB持有的BeanA例項是不完整的??當然不是,因為BeanB持有的是引用,BeanA的初始化完成也意味著BeanB持有的引用初始化完成

為什麼spring只能解決特定場景下的迴圈依賴

1、A和B均通過構造器依賴注入 ----- spring無法解決

A建立例項的時候發現需要注入B物件,然後呼叫B的建立流程,當建立B的時候,檢測到需要注入A,但是此時快取中沒有隻能去建立,因此迴圈依賴無法解決

2、A和B均通過屬性注入 ----- spring可以解決

3、A通過屬性注入B,B通過構造器注入A ----- spring可以解決

4、A通過構造器注入B,B通過屬性注入A ----- spring無法解決

和前面類似,A建立例項的時候發現需要注入B物件,然後呼叫B的建立流程;B是通過屬性注入A,此時能夠建立成功B的例項;接下來會執行填充B的屬性,發現要注入A,執行getBean(A),因為此前A被標記在建立中,因此丟擲異常

示例:

@Component
@Slf4j
public class BeanA {

    @Autowired
    public BeanA(BeanB beanB) {
    }
}
@Component
@Slf4j
public class BeanB {

    @Autowired
    private BeanA beanA;
}

執行結果:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:219)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
    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:1304)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1224)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
    ... 56 more

spring為什麼解決迴圈依賴時用了三級快取,用二級行不行?

我的理解,沒有使用AOP的bean二級快取完全夠用,但是有了AOP,需要用三級快取,也並不是說三級快取是必須的,而是引入三級快取為了使AOP場景下的bean生命週期標準化;至於三級快取效率更高,我沒覺得;

先看看三級快取裡面儲存了什麼:三級快取中在AOP場景下儲存的是代理物件,非AOP場景下儲存的是源物件

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    // 存在AOP代理, 為原來的bean建立代理物件; 否則返回原物件
    // AnnotationAwareAspectJAutoProxyCreator為AOP後置處理器
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要返回代理物件
    return wrapIfNecessary(bean, beanName, cacheKey);
}

在初始化完成後,又呼叫了一次getSingleton方法,這裡allowEarlyReference引數為false,也就是禁用三級快取的含義;

exposedObject == bean是恆等於的,除非沒事閒的蛋疼,自定義了一個BeanPostProcessor,修改了bean;

PS:雖然AnnotationAwareAspectJAutoProxyCreator也是一種後置處理器,並且返回了代理物件,會造成bean的修改,不過這個後置處理器在執行postProcessAfterInitialization方法時,會判斷代理物件是否存在,不存在則返回代理物件;

而在將bean存入三級快取時,實際上就已經建立了代理物件;因此AnnotationAwareAspectJAutoProxyCreator不會影響exposedObject == bean

前面這一坨做個總結:B在注入A時,從三級快取拿到A,並將A存入二級快取;A在注入了B之後,從二級快取將A例項取出(此時的例項是完整的),然後將A存入單例池singletonObjects

回到最開始的問題,為啥非得用三級快取這個工廠物件,直接用二級快取將物件暴露出去不可以麼???三級快取是為了延遲例項化期間生成代理物件;注:這裡的三級快取指的是一個lamba表示式

1、無論是否存在迴圈依賴,只要有AOP,那麼注入的物件一定是代理物件;

2、不存在迴圈依賴時,如果只有二級快取,那麼先建立代理物件,然後存入二級快取;這麼早建立代理物件完全沒必要並且違背AOP和spring結合的生命週期;

3、spring結合AOP的處理類是AnnotationAwareAspectJAutoProxyCreator,是一種後置處理器,並且要求在初始化後建立代理物件

4、因此引入三級快取,提前暴露一個工廠物件,只有在迴圈依賴的時候才提前建立代理物件

三級快取真的提升了效率??

還是以A、B迴圈依賴為例,A被定義為切面,並開啟aop代理

1、三級快取場景

2、假設只有二級快取場景

能夠看到有無三級快取只是建立A代理物件的時機不一樣,並沒有帶來什麼效率提升