Spring如何解決循環依賴

一、背景

我們都知道Spring可以通過xml,或者解析我們的注解,通過掃描所有資源文件,從而將所有匹配到的資源封裝成為一個BeanDefinition注冊到我們的BeanFactory中。
此時,Spring已經知道了所有我們想要注冊到容器中的BeanDefinition,下一步就是將BeanDefinition實例化,這樣才能提供出來給我們使用。

二、Spring中Bean的實例化

我們發現Spring整個加載過程都在AbstractApplicationContext.refresh()中去完成。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
    // Prepare this context for refreshing. 準備刷新
    prepareRefresh();
    
    // Tell the subclass to refresh the internal bean factory.
    /*
     * 刷新內部BeanFactory
     * ClassPathXmlApplicationContext:1.新建BeanFactory,2.解析xml,3.封裝成BeanDefintion對象
     * AnnotationConfigApplicationContext: 獲取GenericApplicationContext中的beanFactory
     */
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    // Prepare the bean factory for use in this context.
    // 為BeanFactory進行必要的準備工作
    prepareBeanFactory(beanFactory);
    
    try {
        // Allows post-processing of the bean factory in context subclasses.
        // 進行額外的后置處理
        postProcessBeanFactory(beanFactory);
    
        // Invoke factory processors registered as beans in the context.
        // 執行1.BeanDefinitionResgistryPostProcessor、2.BeanFactoryPostProcessor的回調
        invokeBeanFactoryPostProcessors(beanFactory);
    
        // Register bean processors that intercept bean creation.
        // 實例化所有實現了BeanPostProcessor接口的類并注冊到容器中去
        registerBeanPostProcessors(beanFactory);
    
        // Initialize message source for this context. 國際化
        initMessageSource();
    
        // Initialize event multicaster for this context. 初始化事件類
        initApplicationEventMulticaster();
    
        // Initialize other special beans in specific context subclasses. 子容器自定義實現
        onRefresh();
    
        // Check for listener beans and register them. 注冊事件
        registerListeners();
    
        // Instantiate all remaining (non-lazy-init) singletons.
        //1.bean實例化,2.ioc 3.注解支持 4.BeanPostProssor執行 5.AOP入口
        finishBeanFactoryInitialization(beanFactory);
    
        // Last step: publish corresponding event.
        finishRefresh();
      } catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                    "cancelling refresh attempt: " + ex);
        }
    
        // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
        // Reset 'active' flag.
                    cancelRefresh(ex);
        // Propagate exception to caller.
        throw ex;
                } finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        resetCommonCaches();
    }
}

我們著重關注一下finishBeanFactoryInitialization方法,它是Spring實例化的入口方法。

  • 獲取BeanFactory中所有的beanDefinition名稱

  • 合并RootBeanDefinition

  • 非抽象的,單例的,非懶加載的就實例化

  • 是否實現了FactoryBean接口,如果是加一個&前綴調用內部的getObject,否則直接獲取

  • 首先嘗試從緩存中獲取getSingleton(beanName),(首次獲取必然獲取不到)接著進入創建方法

  • 單例創建之前的操作:加入到正在創建的一個set集合中singletonsCurrentlyInCreation

  • 調到外部的匿名類中的實例化方法,如果有值已經創建成功singletonFactory.getObject();

  • 調到doCreateBean創建實例BeanWrapper

  • 允許早期引用加入單例工廠直接返回這個bean的引用。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

  • 填充屬性的值populateBean

  • initializeBean

image.png

三、Spring容器如何解決循環依賴

什么是循環依賴

image.png

循環依賴就是循環引用,就是兩個或多個 bean 相互之間的持有對方,比如 CircleA 引用 C ircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,則它們最終反映為一個環。

@Component
public class CircleClassA {
public CircleClassA() {
        System.out.println("====CircleClassA====");
    }
}

@Component
public class CircleClassB {
public CircleClassB() {
        System.out.println("====CircleClassB====");
    }
}

首先我們需要明確的一點是:Spring只會處理上述類型的循環依賴(單例,非構造函數注入)其它情況直接報錯。

Spring在處理Bean實例化的過程中是如何解決循環依賴的呢?我們需要著重關注如下3個Map。

singletonObjects
earlySingletonObjects
singletonFactories

具體步驟如下:

  1. CircleClassA 在實例化的時候 首先從緩存中獲取不到,然后進入創建方法,接著將CircleClassA加入到singletonsCurrentlyInCreation中,并在singletonFactories加入一個getEarlyBeanReference,表示當前CircleClassA正在創建中。

  2. 當CircleClassA填充屬性的值populateBean時,發現依賴了CircleClassB,觸發CircleClassB的實例化。

  3. 實例化CircleClassB,首先從緩存中獲取不到,然后進入創建方法,接著將CircleClassB加入到singletonsCurrentlyInCreation中,并在singletonFactories加入一個getEarlyBeanReference,表示當前CircleClassB正在創建中。

  4. 當CircleClassB填充屬性的值populateBean時,發現依賴了CircleClassA,觸發CircleClassA的實例化。

  5. 再次進入CircleClassA 的實例化方法,此時雖然singletonObjects中獲取不到CircleClassA,但是檢測到CircleClassA存在早期暴露的實例因此嘗試從earlySingletonObjects中獲取,首次調用獲取不到從singletonFactories中獲取,取到之后將CircleClassA放入earlySingletonObjects,并提供給CircleClassB填充屬性的值populateBean時使用。(此時的CircleClassA只是個引用的地址,實際上并不是一個完整的CircleClassA)。

  6. 此時CircleClassB已經完成了(內部依賴的CircleClassA是個不完整的實例)并提供給CircleClassA填充屬性的值populateBean時使用。CircleClassA完成了CircleClassB的注入,它變成了一個完整的實例。

  7. 又由于CircleClassB中引用了CircleClassA的一個地址。所以它也同時變成了一個完整的。

  8. 實例化完成之后刪除早期引用map,并放入單例map中緩存singletonObjects。

image.png

程序員的核心競爭力其實還是技術,因此對技術還是要不斷的學習,關注 “IT 巔峰技術” 公眾號 ,該公眾號內容定位:中高級開發、架構師、中層管理人員等中高端崗位服務的,除了技術交流外還有很多架構思想和實戰案例。

作者是 《 消息中間件 RocketMQ 技術內幕》 一書作者,同時也是 “RocketMQ 上海社區”聯合創始人,曾就職于拼多多、德邦等公司,現任上市快遞公司架構負責人,主要負責開發框架的搭建、中間件相關技術的二次開發和運維管理、混合云及基礎服務平臺的建設。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容