IoC 之裝載 BeanDefinitions 總結

  • 2019 年 10 月 13 日
  • 筆記

最近時間重新對spring源碼進行了解析,以便後續自己能夠更好的閱讀spring源碼,想要一起深入探討請加我QQ:1051980588

1 ClassPathResource resource = new ClassPathResource("bean.xml");  2 DefaultListableBeanFactory factory = new DefaultListableBeanFactory();  3 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);  4 reader.loadBeanDefinitions(resource);

對spring源碼解析上面是最基本的幾行程式碼,接下來我會對這基本程式碼深入探索,當然有些程式碼解釋是基於其他部落格借鑒過來的,如有相同希望見諒

  • ClassPathResource resource = new ClassPathResource("bean.xml"); : 根據 Xml 配置文件創建 Resource 資源對象。ClassPathResource 是 Resource 介面的子類,bean.xml 文件中的內容是我們定義的 Bean 資訊。
  • DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); :創建一個 BeanFactory 。DefaultListableBeanFactory 是 BeanFactory 的一個子類,BeanFactory 作為一個介面,其實它本身是不具有獨立使用的功能的,而 DefaultListableBeanFactory 則是真正可以獨立使用的 IoC 容器,它是整個 Spring IoC 的始祖,在後續會有專門的文章來分析它。
  • XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); :創建 XmlBeanDefinitionReader 讀取器,用於載入 BeanDefinition 。
  • reader.loadBeanDefinitions(resource);:開始 BeanDefinition 的載入和註冊進程,完成後的 BeanDefinition 放置在 IoC 容器中。

1. Resource 定位

Spring 為了解決資源定位的問題,提供了兩個介面:Resource、ResourceLoader,其中:

  • Resource 介面是 Spring 統一資源的抽象介面
  • ResourceLoader 則是 Spring 資源載入的統一抽象。

Resource 資源的定位需要 Resource 和 ResourceLoader 兩個介面互相配合,在上面那段程式碼中 new ClassPathResource("bean.xml") 為我們定義了資源,那麼 ResourceLoader 則是在什麼時候初始化的呢?看 XmlBeanDefinitionReader 構造方法:

1 // XmlBeanDefinitionReader.java  2 public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {  3     super(registry);  4 }

我們可以看見 直接調用父類 AbstractBeanDefinitionReader 構造方法,程式碼如下:

 1 // AbstractBeanDefinitionReader.java   2   3 protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {   4     Assert.notNull(registry, "BeanDefinitionRegistry must not be null");   5     this.registry = registry;   6     // Determine ResourceLoader to use.   7     if (this.registry instanceof ResourceLoader) {   8         this.resourceLoader = (ResourceLoader) this.registry;   9     }    else {  10         this.resourceLoader = new PathMatchingResourcePatternResolver();  11     }  12  13     // Inherit Environment if possible  14     if (this.registry instanceof EnvironmentCapable) {  15         this.environment = ((EnvironmentCapable) this.registry).getEnvironment();  16     } else {  17         this.environment = new StandardEnvironment();  18     }  19 }

核心在於設置 resourceLoader 這段,如果設置了 ResourceLoader 則用設置的,否則使用 PathMatchingResourcePatternResolver ,該類是一個集大成者的 ResourceLoader。

2. BeanDefinition 的載入和解析

reader.loadBeanDefinitions(resource); 程式碼段,開啟 BeanDefinition 的解析過程。如下:

1 // XmlBeanDefinitionReader.java  2 @Override  3 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {  4     return loadBeanDefinitions(new EncodedResource(resource));  5 }

 

在這個方法會將資源 resource 包裝成一個 EncodedResource 實例對象,然後調用 #loadBeanDefinitions(EncodedResource encodedResource) 方法。而將 Resource 封裝成 EncodedResource 主要是為了對 Resource 進行編碼,保證內容讀取的正確性。程式碼如下:

 1 // XmlBeanDefinitionReader.java   2   3 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {   4     // ... 省略一些程式碼   5     try {   6         // 將資源文件轉為 InputStream 的 IO 流   7         InputStream inputStream = encodedResource.getResource().getInputStream();   8         try {   9             // 從 InputStream 中得到 XML 的解析源  10             InputSource inputSource = new InputSource(inputStream);  11             if (encodedResource.getEncoding() != null) {  12                 inputSource.setEncoding(encodedResource.getEncoding());  13             }  14             // ... 具體的讀取過程  15             return doLoadBeanDefinitions(inputSource, encodedResource.getResource());  16         }  17         finally {  18             inputStream.close();  19         }  20     }  21     // 省略一些程式碼  22 }

 

從 encodedResource 源中獲取 xml 的解析源,然後調用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,執行具體的解析過程。

// XmlBeanDefinitionReader.java    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)              throws BeanDefinitionStoreException {      try {          // 獲取 XML Document 實例          Document doc = doLoadDocument(inputSource, resource);          // 根據 Document 實例,註冊 Bean 資訊          int count = registerBeanDefinitions(doc, resource);          return count;      }      // ... 省略一堆配置  }

2.1 轉換為 Document 對象

調用在上面方法中 #doLoadDocument(InputSource inputSource, Resource resource) 方法,會將 Bean 定義的資源轉換為 Document 對象。程式碼如下:

// XmlBeanDefinitionReader.java    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {      return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,              getValidationModeForResource(resource), isNamespaceAware());  }

該方法接受五個參數:

  • inputSource :載入 Document 的 Resource 源。
  • entityResolver :解析文件的解析器。
  • errorHandler :處理載入 Document 對象的過程的錯誤。
  • validationMode :驗證模式。
  • namespaceAware :命名空間支援。如果要提供對 XML 名稱空間的支援,則為 true 

#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,在類 DefaultDocumentLoader 中提供了實現。程式碼如下:

 1 // DefaultDocumentLoader.java   2   3 @Override   4 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,   5         ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {   6     // 創建 DocumentBuilderFactory   7     DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);   8     // 創建 DocumentBuilder   9     DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);  10     // 解析 XML InputSource 返回 Document 對象  11     return builder.parse(inputSource);  12 }

2.2 註冊 BeanDefinition 流程

這到這裡,就已經將定義的 Bean 資源文件,載入並轉換為 Document 對象了。那麼,下一步就是如何將其解析為 SpringIoC 管理的 BeanDefinition 對象,並將其註冊到容器中。這個過程由方法 #registerBeanDefinitions(Document doc, Resource resource) 方法來實現。程式碼如下

 1 // XmlBeanDefinitionReader.java   2   3 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {   4     // 創建 BeanDefinitionDocumentReader 對象   5     BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();   6     // 獲取已註冊的 BeanDefinition 數量   7     int countBefore = getRegistry().getBeanDefinitionCount();   8     // 創建 XmlReaderContext 對象   9     // 註冊 BeanDefinition  10     documentReader.registerBeanDefinitions(doc, createReaderContext(resource));  11     // 計算新註冊的 BeanDefinition 數量  12     return getRegistry().getBeanDefinitionCount() - countBefore;  13 }

(1)首先,創建 BeanDefinition 的解析器 BeanDefinitionDocumentReader 。

(2)然後,調用該 BeanDefinitionDocumentReader 的 #registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法,開啟解析過程,這裡使用的是委派模式,具體的實現由子類 DefaultBeanDefinitionDocumentReader 完成。程式碼如下:

1 // DefaultBeanDefinitionDocumentReader.java  2  3 @Override  4 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {  5     this.readerContext = readerContext;  6     // 獲得 XML Document Root Element  7     // 執行註冊 BeanDefinition  8     doRegisterBeanDefinitions(doc.getDocumentElement());  9 }


2.2.1 對 Document 對象的解析從 Document 對象中獲取根元素 root,然後調用 #doRegisterBeanDefinitions(Element root)` 方法,開啟真正的解析過程。程式碼如下:
// DefaultBeanDefinitionDocumentReader.java    protected void doRegisterBeanDefinitions(Element root) {      // ... 省略部分程式碼(非核心)      this.delegate = createDelegate(getReaderContext(), root, parent);        // 解析前處理      preProcessXml(root);      // 解析      parseBeanDefinitions(root, this.delegate);      // 解析後處理      postProcessXml(root);    }

 

#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 是對根元素 root 的解析註冊過程。程式碼如下:

// DefaultBeanDefinitionDocumentReader.java    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {      // 如果根節點使用默認命名空間,執行默認解析      if (delegate.isDefaultNamespace(root)) {          // 遍歷子節點          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;                  // 如果該節點使用默認命名空間,執行默認解析                  if (delegate.isDefaultNamespace(ele)) {                      parseDefaultElement(ele, delegate);                  // 如果該節點非默認命名空間,執行自定義解析                  } else {                      delegate.parseCustomElement(ele);                  }              }          }      // 如果根節點非默認命名空間,執行自定義解析      } else {          delegate.parseCustomElement(root);      }  }

 

迭代 root 元素的所有子節點,對其進行判斷:

  • 若節點為默認命名空間,則調用 #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,開啟默認標籤的解析註冊過程。詳細解析
  • 否則,調用 BeanDefinitionParserDelegate#parseCustomElement(Element ele) 方法,開啟自定義標籤的解析註冊過程。

2.2.1.1 默認標籤解析

若定義的元素節點使用的是 Spring 默認命名空間,則調用 #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,進行默認標籤解析。程式碼如下:

// DefaultBeanDefinitionDocumentReader.java    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {      if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // import          importBeanDefinitionResource(ele);      } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // alias          processAliasRegistration(ele);      } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // bean          processBeanDefinition(ele, delegate);      } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans          // recurse          doRegisterBeanDefinitions(ele);      }  }

對四大標籤:<import><alias><bean><beans> 進行解析。

2.2.1.2 自定義標籤解析

對於默認標籤則由 parseCustomElement(Element ele) 方法,負責解析。程式碼如下:

 1 // BeanDefinitionParserDelegate.java   2   3 @Nullable   4 public BeanDefinition parseCustomElement(Element ele) {   5     return parseCustomElement(ele, null);   6 }   7   8 @Nullable   9 public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {  10     // 獲取 namespaceUri  11     String namespaceUri = getNamespaceURI(ele);  12     if (namespaceUri == null) {  13         return null;  14     }  15     // 根據 namespaceUri 獲取相應的 Handler  16     NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);  17     if (handler == null) {  18         error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);  19         return null;  20     }  21     // 調用自定義的 Handler 處理  22     return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));  23 }

 

獲取節點的 namespaceUri,然後根據該 namespaceUri 獲取相對應的 NamespaceHandler,最後調用 NamespaceHandler 的 #parse(Element element, ParserContext parserContext) 方法,即完成自定義標籤的解析和注入。

2.2.2 註冊 BeanDefinition

經過上面的解析,則將 Document 對象裡面的 Bean 標籤解析成了一個個的 BeanDefinition ,下一步則是將這些 BeanDefinition 註冊到 IoC 容器中。動作的觸發是在解析 Bean 標籤完成後,程式碼如下

// DefaultBeanDefinitionDocumentReader.java    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {      // 進行 bean 元素解析。      // 如果解析成功,則返回 BeanDefinitionHolder 對象。而 BeanDefinitionHolder 為 name 和 alias 的 BeanDefinition 對象      // 如果解析失敗,則返回 null 。      BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);      if (bdHolder != null) {          // 進行自定義標籤處理          bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);          try {              // 進行 BeanDefinition 的註冊              // Register the final decorated instance.              BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());          } catch (BeanDefinitionStoreException ex) {              getReaderContext().error("Failed to register bean definition with name '" +                      bdHolder.getBeanName() + "'", ele, ex);          }          // 發出響應事件,通知相關的監聽器,已完成該 Bean 標籤的解析。          // Send registration event.          getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));      }  }

 

調用 BeanDefinitionReaderUtils.registerBeanDefinition() 方法,來註冊。其實,這裡面也是調用 BeanDefinitionRegistry 的 #registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法,來註冊 BeanDefinition 。不過,最終的實現是在 DefaultListableBeanFactory 中實現,程式碼如下:

 1 // DefaultListableBeanFactory.java   2 @Override   3 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)   4         throws BeanDefinitionStoreException {   5     // ...省略校驗相關的程式碼   6     // 從快取中獲取指定 beanName 的 BeanDefinition   7     BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);   8     // 如果已經存在   9     if (existingDefinition != null) {  10         // 如果存在但是不允許覆蓋,拋出異常  11         if (!isAllowBeanDefinitionOverriding()) {  12              throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);  13         } else {  14            // ...省略 logger 列印日誌相關的程式碼  15         }  16         // 【重點】允許覆蓋,直接覆蓋原有的 BeanDefinition 到 beanDefinitionMap 中。  17         this.beanDefinitionMap.put(beanName, beanDefinition);  18     // 如果未存在  19     } else {  20         // ... 省略非核心的程式碼  21         // 【重點】添加到 BeanDefinition 到 beanDefinitionMap 中。  22         this.beanDefinitionMap.put(beanName, beanDefinition);  23     }  24     // 重新設置 beanName 對應的快取  25     if (existingDefinition != null || containsSingleton(beanName)) {  26         resetBeanDefinition(beanName);  27     }  28 }

 

這段程式碼最核心的部分是這句 this.beanDefinitionMap.put(beanName, beanDefinition) 程式碼段。所以,註冊過程也不是那麼的高大上,就是利用一個 Map 的集合對象來存放:key 是 beanName ,value 是 BeanDefinition 對象

3. 小結

至此,整個 IoC 的初始化過程就已經完成了,從 Bean 資源的定位,轉換為 Document 對象,接著對其進行解析,最後註冊到 IoC 容器中,都已經完美地完成了。現在 IoC 容器中已經建立了整個 Bean 的配置資訊,這些 Bean 可以被檢索、使用、維護,他們是控制反轉的基礎,是後面注入 Bean 的依賴。最後用一張流程圖來結束這篇總結之文。