Spring源碼分析之-載入IOC容器

  • 2019 年 10 月 3 日
  • 筆記

本文接上一篇文章 SpringIOC 源碼,控制反轉前的處理(https://mp.weixin.qq.com/s/9RbVP2ZQVx9-vKngqndW1w) 繼續進行下面的分析

首先貼出 Spring bean容器的刷新的核心 11個步驟進行祭拜(一定要讓我學會了...阿門)

// 完成IoC容器的創建及初始化工作  @Override  public void refresh() throws BeansException, IllegalStateException {    synchronized (this.startupShutdownMonitor) {        // 1: 刷新前的準備工作。      prepareRefresh();        // 告訴子類刷新內部bean 工廠。      //  2:創建IoC容器(DefaultListableBeanFactory),載入解析XML文件(最終存儲到Document對象中)      // 讀取Document對象,並完成BeanDefinition的載入和註冊工作      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();        //  3: 對IoC容器進行一些預處理(設置一些公共屬性)      prepareBeanFactory(beanFactory);        try {          //  4:  允許在上下文子類中對bean工廠進行後處理。        postProcessBeanFactory(beanFactory);          //  5: 調用BeanFactoryPostProcessor後置處理器對BeanDefinition處理        invokeBeanFactoryPostProcessors(beanFactory);          //  6: 註冊BeanPostProcessor後置處理器        registerBeanPostProcessors(beanFactory);          //  7: 初始化一些消息源(比如處理國際化的i18n等消息源)        initMessageSource();          //  8: 初始化應用事件多播器        initApplicationEventMulticaster();          //  9: 初始化一些特殊的bean        onRefresh();          //  10: 註冊一些監聽器        registerListeners();          //  11: 實例化剩餘的單例bean(非懶載入方式)        //      注意事項:Bean的IoC、DI和AOP都是發生在此步驟        finishBeanFactoryInitialization(beanFactory);          //  12: 完成刷新時,需要發布對應的事件        finishRefresh();      }        catch (BeansException ex) {        if (logger.isWarnEnabled()) {          logger.warn("Exception encountered during context initialization - " +                      "cancelling refresh attempt: " + ex);        }          // 銷毀已經創建的單例避免佔用資源        destroyBeans();          // 重置'active' 標籤。        cancelRefresh(ex);          // 傳播異常給調用者        throw ex;      }        finally {          // 重置Spring核心中的常見內省快取,因為我們可能不再需要單例bean的元數據了...        resetCommonCaches();      }    }  }

下面來分析上述流程中的第二個步驟: 創建並解析IOC容器

我們開始進入分析,首先會調用到 AbstractApplicationContext 中的 obtainFreshBeanFactory 方法,此方法會進行IOC容器的刷新並取出BeanFactory,程式碼如下

// 告訴內部子類刷新內部的bean factory  protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {          // 主要是通過該方法完成IoC容器的刷新          // ClassPathXmlApplicationContext.refreshBeanFactory 調用的是 AbstractRefreshApplicationContext          // AnnotationConfigApplicationContext.refreshBeanFactory調用的是GenericApplicationContext          refreshBeanFactory();          ConfigurableListableBeanFactory beanFactory = getBeanFactory();      ...          return beanFactory;      }
  1. 首先第一步, refreshBeanFactory,刷新Bean工廠,refreshBeanFactory是一個介面,此介面在 AbstractApplicationContext 中進行定義,先看一下這個JavaDoc對這個介面的定義
/**       * 子類必須實現這個方法才能執行實際的配置載入。此方法在任何其他初始化工作開始之前由refresh()方法調用       * 一個子類創建了一個新的 bean factory 並且持有對它的引用,或者返回它擁有的單個BeanFactory實例。       * 在後一種情況下,如果多次刷新上下文,它通常會拋出 IllegalStateException。       */      protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

它有兩個實現類,默認的是 AbstractRefreshableApplicationContext 類,它的refreshBeanFactory方法如下

@Override  protected final void refreshBeanFactory() throws BeansException {    // 如果之前有IoC容器,則銷毀    if (hasBeanFactory()) {      destroyBeans();      closeBeanFactory();    }    try {      // 創建IoC容器,也就是 DefaultListableBeanFactory, 初始化AbstractBeanFactory      // 註冊BeanNameAware,BeanClassLoaderAware,BeanFactoryAware, 設置當前BeanFactory      DefaultListableBeanFactory beanFactory = createBeanFactory();      beanFactory.setSerializationId(getId());      // 設置工廠的屬性:是否允許BeanDefinition覆蓋和是否允許循環依賴      customizeBeanFactory(beanFactory);      // 調用載入BeanDefinition的方法,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器      loadBeanDefinitions(beanFactory);      synchronized (this.beanFactoryMonitor) {        this.beanFactory = beanFactory;      }    }    catch (IOException ex) {      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);    }  }

如果之前有IOC容器的話,就進行銷毀,並且關閉容器。適用於第二次調用ClassPathXmlApplicationContext("配置文件"),由於是第一次載入配置文件,還未創建BeanFactory,所以hasBeanFactory()返回false。

下面這一步就是創建IOC容器,也就是DefaultListableBeanFactory

// 為上下文創建一個內部工廠,默認的實現創建了一個內部的DefaultListableBeanFactory.getInternalParentBeanFactory(),  // 在子類中被重寫,例如自定義DefaultListableBeanFactory的設置。  protected DefaultListableBeanFactory createBeanFactory() {    return new DefaultListableBeanFactory(getInternalParentBeanFactory());  }

上述方法傳遞的是一個getInternalParentBeanFactory()方法,我們先來看一下這個方法:

protected BeanFactory getInternalParentBeanFactory() {    return (getParent() instanceof ConfigurableApplicationContext) ?      ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent();  }

getParent()這個方法會返回父類上下文,如果沒有(父類)的話,返回null(也就表明這個上下文是繼承樹的根節點。)這句程式碼的意思也就是說如果它實現了 ConfigurableApplicationContext,則返回父類上下文的BeanFactory,如果沒有的話,就返回父上下文本身。因為沒有設置過父節點上下文,所以此時的getInternalParentBeanFactory()方法返回為null。

下一步返回一個DefaultListableBeanFactory,首先來看一下DefaultListableBeanFactory的繼承體系

它的調用流程如下:

  • 在AbstractRefreshableApplicationContext類中返回一個DefaultListableBeanFactory的構造函數
  • DefaultListableBeanFactory構造函數中調用AbstractAutowireCapableBeanFactory的構造函數
  • AbstractAutowireCapableBeanFactory帶參數的構造函數會調用AbstractAutowireCapableBeanFactory無參數的構造函數,並註冊了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware介面。並設置setParentBeanFactory(parentBeanFactory) 為null(parentBeanFactory就是上面getInternalParentBeanFactory()方法的返回值);

  • AbstractAutowireCapableBeanFactory會調用父類AbstractBeanFactory無參構造方法並初始化。

此時的DefaultListableBeanFactory會帶上一系列的初始化欄位。Bean工廠說的也就是DefaultListableBeanFactory

  1. 在createBeanFactory()之後,會給ClassPathXmlApplicationContext設置全局唯一Id
beanFactory.setSerializationId(getId());
  1. 第三步,設置DefaultListableBeanFactory的屬性,是否允許重寫Bean的定義資訊和是否允許循環依賴。沒有設置,默認為null
/*  * 通過上下文自定義內部bean工廠。 嘗試調用每一個refresh 方法。       * 默認實現應用此上下文setAllowBeanDefinitionOverriding 是否允許Bean定義重寫       * 和 setAllowCircularReferences 是否允許循環依賴設置。       * 如果特殊情況,可以在子類中重寫以任何自定義DefaultListableBeanFactory 設置。  */  protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {          if (this.allowBeanDefinitionOverriding != null) {              beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);          }          if (this.allowCircularReferences != null) {              beanFactory.setAllowCircularReferences(this.allowCircularReferences);          }      }
  1. 最關鍵的一步,載入Bean的定義資訊。所有關於Bean Definitions都會在這一步的這個方法進行解析。調用載入BeanDefinition的方法,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器AbstractXmlApplicationContext 中的loadBeanDefinitions方法,具體程式碼如下
// 通過XmlBeanDefinitionReader 載入bean 定義資訊。  @Override  protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {    // 創建一個BeanDefinition閱讀器,通過閱讀XML文件,真正完成BeanDefinition的載入和註冊    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);      // 配置bean 定義閱讀器和上下文資源載入環境。    beanDefinitionReader.setEnvironment(this.getEnvironment());    beanDefinitionReader.setResourceLoader(this);    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));      // 允許子類提供自定義初始化的reader,然後繼續載入bean定義資訊。    initBeanDefinitionReader(beanDefinitionReader);      // 委託給BeanDefinition閱讀器去載入BeanDefinition    loadBeanDefinitions(beanDefinitionReader);  }
  • 首先來看第一步,創建XmlBeanDefinitionReader並進行BeanDefinition的載入和註冊。初始化XmlBeanDefinitionReader(beanFactory)構造方法,然後調用AbstractBeanDefinitionReader(BeanDefinitionRegistry registry)的構造方法,因為DefaultListableBeanFactory實現了BeanDefinitionRegistry介面,所以就把DefaultListableBeanFactory當作一個BeanDefinitionRegistry傳遞進去。下面看程式碼
/* 通過bean 工廠創建一個新的 AbstractBeanDefinitionReader。       如果傳入的bean 工廠不僅實現了BeanDefinitionRegistry 介面還實現了ResourceLoader 介面。     將會使用默認的ResourceLoader。 這是 ApplicationContext 實現的情況。     如果給定一個普通的BeanDefinitionRegistry,則默認的 ResourceLoader 會是PathMatchingResourcePatternResolver     如果傳入的bean 工廠實現了 EnvironmentCapable,則此讀取器將使用此環境。否則,這個讀取器     將會初始化並且使用 StandardEnvironment。 所有ApplicationContext的實現都是 EnvironmentCapable,       然而通常BeanFactory 的實現則不是。   */  protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {    ...    if (this.registry instanceof ResourceLoader) {      this.resourceLoader = (ResourceLoader) this.registry;    }    else {      this.resourceLoader = new PathMatchingResourcePatternResolver();    }    if (this.registry instanceof EnvironmentCapable) {      this.environment = ((EnvironmentCapable) this.registry).getEnvironment();    }    else {      this.environment = new StandardEnvironment();    }  }

上面程式碼的意思就是,如果BeanDefinitionRegistry實現了ResourceLoader介面,就會強制轉換為ResourceLoader,否則,將會new 一個PathMatchingResourcePatternResolver(),如果實現了EnvironmentCapable介面,就直接獲取系統環境,否則就直接new一個StandardEnvironment。

  • 完成第一步之後,然後配置bean閱讀器和上下文資源載入環境,允許子類提供自定義初始化的reader,然後繼續載入bean定義資訊。這一步希望子類實現自定義的bean載入資訊。
  • 最後一步進行真正意義上當bean載入,委託給BeanDefinition閱讀器去載入BeanDefinition,來看具體的解析過程
// 委託給XmlBeanDefinition閱讀器去載入BeanDefinition   // loadBeanDefinitions(beanDefinitionReader);  ↓      @Override      public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {          // 將讀入的XML資源進行特殊編碼處理          return loadBeanDefinitions(new EncodedResource(resource));      }      // loadBeanDefinitions  ↓        // loadBeanDefinitions(resources) 經過一系列的調用最終會調用到下面的程式碼    // XmlBeanDefinitionReader 部分程式碼省略      public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {          ...      // 從InputStream中得到XML的解析源      InputSource inputSource = new InputSource(inputStream);      if (encodedResource.getEncoding() != null) {         inputSource.setEncoding(encodedResource.getEncoding());      }      // 這裡是具體的讀取過程      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());      }      // doLoadBeanDefinitions ↓    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)              throws BeanDefinitionStoreException {                // 通過DOM4J載入解析XML文件,最終形成Document對象              Document doc = doLoadDocument(inputSource, resource);          // 通過對Document對象的操作,完成BeanDefinition的載入和註冊工作          return registerBeanDefinitions(doc, resource);      }      // registerBeanDefinitions  ↓    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {          // 創建BeanDefinitionDocumentReader來解析Document對象,完成BeanDefinition解析          BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();          // 獲得容器中已經註冊的BeanDefinition數量          int countBefore = getRegistry().getBeanDefinitionCount();          // 解析過程入口,BeanDefinitionDocumentReader只是個介面,具體的實現過程在DefaultBeanDefinitionDocumentReader完成          documentReader.registerBeanDefinitions(doc, createReaderContext(resource));          // 統計新的的BeanDefinition數量          return getRegistry().getBeanDefinitionCount() - countBefore;      }

到這一步仍舊只是創建了各種需要解析的閱讀器對象,文檔解析對象,解析之前的準備工作,

這裡說一下BeanDefinitionDocumentReader 這個介面:

/*   * 每個要解析的文檔實例化:實現可以在執行   * registerBeanDefinitions 方法時保存實例變數中的狀態   * 例如,為文檔中的所有bean定義定義的全局設置。  */  public interface BeanDefinitionDocumentReader {    /*    * 從 DOM 文檔中讀取Bean 定義資訊並且通過閱讀器上下文環境把它們註冊進registry中    */    void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)              throws BeanDefinitionStoreException;  }

它的默認實現類只有一個,就是 DefaultBeanDefinitionDocumentReader ,此類負責做真正的bean 解析工作。

下面registerBeanDefinitions之後的方法會完成真正的bean解析工作:

DefaultBeanDefinitionDocumentReader.java

// 這個實現用來解析 spring-beans 約束  // 打開DOM 文檔,初始化默認<beans/>的設置;然後解析其中的bean定義資訊。  @Override  public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {    this.readerContext = readerContext;    logger.debug("Loading bean definitions");    // 獲得Document的根元素<beans>標籤    Element root = doc.getDocumentElement();    // 真正實現BeanDefinition解析和註冊工作    doRegisterBeanDefinitions(root);  }    //      doRegisterBeanDefinitions(root);  ↓  protected void doRegisterBeanDefinitions(Element root) {            /*              任何嵌套的<beans>元素都將導致此方法中的遞歸。 為了正確傳播和保留<beans>default-* 屬性,跟蹤當前的              父委託,可能為null。              創建新的(子)委託,引用父項以進行回退,然後最終將this.delegate重置為其原始(父)引用。 此行為模擬了一堆代理,而實際上並不需要一個代理。           */            // 這裡使用了委託模式,將具體的BeanDefinition解析工作交給了BeanDefinitionParserDelegate去完成          BeanDefinitionParserDelegate parent = this.delegate;          this.delegate = createDelegate(getReaderContext(), root, parent);            // 判斷該根標籤是否包含http://www.springframework.org/schema/beans默認命名空間          ...              // 在解析Bean定義之前,進行自定義的解析,增強解析過程的可擴展性          preProcessXml(root);          // 委託給BeanDefinitionParserDelegate,從Document的根元素開始進行BeanDefinition的解析          parseBeanDefinitions(root, this.delegate);          // 在解析Bean定義之後,進行自定義的解析,增加解析過程的可擴展性          postProcessXml(root);            this.delegate = parent;      }

看到這裡已經真心不容易了,需要休息一下嗎?怎麼說呢,源碼這個東西是需要耐住性子並且需要你投入大量精力的,看過一遍沒有印象太正常了,並且看源碼的前提是沒人打擾,需要一個極度安靜的環境。

tips: 戴上耳機會好很多

在回到正題之前,我先來問你一個問題,你知道代理模式和委託模式的區別嗎?

我是這樣理解的

委託模式是自己不做這件事情呢,而是把事情交給別人去做
代理模式是讓別人搭把手,而自己是真正做這件事情的主角,因為代理實現類(實現了InvocationHandler 的類)只是個打嘴炮的人。

回到正題,在真正做解析工作的時候,會首先創建一個委託類BeanDefinitionParserDelegate ,那麼先來認識一下這個類。

// 用於解析XML bean定義的有狀態委託類。 旨在供主解析器和任何擴展使用  public class BeanDefinitionParserDelegate {      // 此方法也就是委託給 BeanDefinitionParserDelegate 去做的事情    protected BeanDefinitionParserDelegate createDelegate(              XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {            BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);          delegate.initDefaults(root, parentDelegate);          return delegate;      }  }

它主要完成的事情在initDefaults 方法中,一起看下這個方法

/*       * 初始化默認的 懶載入,自動注入,依賴檢查設置, 初始化方法,銷毀方法和合併設置。       * 如果未在本地顯式設置默認值,則通過回退到給定父級來支援嵌套的「beans」元素用例。       */  public void initDefaults(Element root, @Nullable BeanDefinitionParserDelegate parent) {    populateDefaults(this.defaults, (parent != null ? parent.defaults : null), root);    this.readerContext.fireDefaultsRegistered(this.defaults);  }
/**       * in case the defaults are not explicitly set locally.       * 使用默認的 lazy-init,autowire,依賴檢查設置,init-method,destroy-method 和 合併設置 屬性 填充給定的       * DocumentDefaultsDefinition,如果未在本地顯式設置默認值,則支援嵌套的'beans'元素用例,返回parentDefaults       */      protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) {          // default-lazy-init          String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE);          if (DEFAULT_VALUE.equals(lazyInit)) {              // Potentially inherited from outer <beans> sections, otherwise falling back to false.              lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE);          }          defaults.setLazyInit(lazyInit);        // 餘下的程式碼和lazyinit 的邏輯相同,也是解析方法注釋中的各種屬性。 這裡礙於篇幅原因暫不貼出了          ...      //            defaults.setSource(this.readerContext.extractSource(root));      }

然後方法一直返回到創建委託的入口也就是 BeanDefinitionParserDelegate parent = this.delegate;,然後是 Spring 留給開發人員的兩個介面,用於開發人員自己實現

...  // 在解析Bean定義之前,進行自定義的解析,增強解析過程的可擴展性  preProcessXml(root);  // 委託給 BeanDefinitionParserDelegate,從Document的根元素開始進行BeanDefinition的解析  parseBeanDefinitions(root, this.delegate);  // 在解析Bean定義之後,進行自定義的解析,增加解析過程的可擴展性  postProcessXml(root);

我們再來看一下 parseBeanDefinitions(root, this.delegate); 這個方法是交給剛剛創建的 BeanDefinitionParserDelegate 委託用來解析具體的 lazy-init 等屬性的標籤的

/**       * 解析文檔對象的根目錄節點: import, alias, bean       */  protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {    // 載入的Document對象是否使用了Spring默認的XML命名空間(beans命名空間)    if (delegate.isDefaultNamespace(root)) {      // 獲取Document對象根元素的所有子節點(bean標籤、import標籤、alias標籤和其他自定義標籤context、aop等)      NodeList nl = root.getChildNodes();      for (int i = 0; i < nl.getLength(); i++) {        Node node = nl.item(i);        if (node instanceof Element) {          Element ele = (Element) node;          // bean標籤、import標籤、alias標籤,則使用默認解析規則          if (delegate.isDefaultNamespace(ele)) {            parseDefaultElement(ele, delegate);          }          else {//像context標籤、aop標籤、tx標籤,則使用用戶自定義的解析規則解析元素節點            delegate.parseCustomElement(ele);          }        }      }    }    else {      // 如果不是默認的命名空間,則使用用戶自定義的解析規則解析元素節點      delegate.parseCustomElement(root);    }  }

上述步驟主要分為三步:

  • 先判斷是否委託類使用Spring默認的XML命名空間(beans命名空間),在判斷如果取出來的節點是bean、import、alias的話就使用默認的解析規則
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {    // 解析<import>標籤    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {      importBeanDefinitionResource(ele);    }    // 解析<alias>標籤    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {      processAliasRegistration(ele);    }    // 解析<bean>標籤    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {      processBeanDefinition(ele, delegate);    }    // 解析內置<bean>標籤    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {      // recurse      // 遞歸調用      doRegisterBeanDefinitions(ele);    }  }
  • 如果判斷取出來的不是默認節點,則會使用自定義的默認解析規則,context標籤,aop標籤, tx標籤則會默認使用此解析方法
@Nullable  public BeanDefinition parseCustomElement(Element ele) {    // 解析自定義標籤    return parseCustomElement(ele, null);  }    @Nullable  public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {    // 獲取命名空間URI(就是獲取beans標籤的xmlns:aop或者xmlns:context屬性的值)    String namespaceUri = getNamespaceURI(ele);    if (namespaceUri == null) {      return null;    }    // 根據不同的命名空間URI,去匹配不同的NamespaceHandler(一個命名空間對應一個NamespaceHandler)    // 此處會調用DefaultNamespaceHandlerResolver類的resolve方法    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);    if (handler == null) {      error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);      return null;    }    // 調用匹配到的NamespaceHandler的解析方法    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));  }
  • 如果不是使用 beans 的命名空間,也會使用自定義的解析方法進行解析

然後方法解析完成,一直返回到 AbstractApplicationContext 中的 刷新 bean Factory 的方法

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {    // 主要是通過該方法完成IoC容器的刷新    // ClassPathXmlApplicationContext.refreshBeanFactory 調用的是 AbstractRefreshApplicationContext    // AnnotationConfigApplicationContext.refreshBeanFactory調用的是GenericApplicationContext    refreshBeanFactory();    ConfigurableListableBeanFactory beanFactory = getBeanFactory();    if (logger.isDebugEnabled()) {      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);    }    return beanFactory;  }

這樣取出的 Bean 對象就可以進行使用了。

下一篇文章來 解析一下第三個步驟: 對IoC容器進行一些預處理

Tags: