Spring原始碼之容器的基本實現

神祕傑克發表於2022-03-26

開篇

本文主要基於SpringFramework5.2.0.RELEASE版本,原始碼的下載步驟在別的文章中已經講過,這裡就不再贅述。

容器的基本用法

我們先建立一個簡單的示例來看一下容器的基本用法。

建立一個簡單的 Java Bean。

/**
 * @author 神祕傑克
 * 公眾號: Java菜鳥程式設計師
 * @date 2022/3/15
 * @Description 簡單的bean例項
 */
public class MyTestBean {

   private String testStr = "testStr";

   public String getTestStr() {
      return testStr;
   }

   public void setTestStr(String testStr) {
      this.testStr = testStr;
   }
}

建立一個簡單 Spring 配置檔案。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="myTestBean" class="cn.jack.MyTestBean"/>
</beans>

ok,編寫一個測試類進行測試。

/**
 * @author 神祕傑克
 * 公眾號: Java菜鳥程式設計師
 * @date 2022/3/15
 * @Description 測試類
 */
@SuppressWarnings("deprecation")
public class BeanFactoryTest {

   @Test
   public void testSimpleLoad(){
      final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
      final MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
      assertEquals("testStr",myTestBean.getTestStr());
   }

}

執行之後可以看到執行成功,示例就這麼簡單。

執行結果

平常我們並不會直接使用 BeanFactory,而是使用 ApplicationContext,這裡只是為了之後可以更好地分析 Spring 原理。

功能分析

這部分程式碼完成的功能如下:

  • 讀取配置檔案 beanFactoryTest.xml
  • 根據配置檔案中的配置找到對應類的配置,並例項化
  • 呼叫例項化後的例項

按照這些描述,我們可以瞭解需要至少三個類來實現。

  • ConfigReader:用於讀取及驗證配置檔案,隨後放到記憶體中
  • ReflectionUtil:根據配置檔案內容,進行反射例項化,也就是我們配置檔案中的<bean id="myTestBean" class="cn.jack.MyTestBean"/>
  • App:用於完成整個邏輯的串聯

最簡單的Spring架構

按照原始的思維方式,整個過程就是這樣,但是這麼優秀的 Spring 框架的結構組成是什麼樣子?

Spring 的層級架構

我們首先嚐試梳理 Spring 的框架結構,用全域性的角度瞭解 Spring 的構成。

Beans 包的層級架構

實現剛才的示例,我們主要使用的就是 org.springframework.beans.jar。

分析原始碼前,我們首先了解兩個核心類。

1.DefaultListableBeanFactory

我們剛才使用的 XmlBeanFactory 繼承自DefaultListableBeanFactory,而 DefaultListableBeanFactory 是整個載入的核心部分,是 Spring 註冊及載入 bean 的預設實現,而在 XmlBeanFactory 中使用了自定義的 XML 讀取器XmlBeanDefinitionReader,實現了個性化的 BeanDefinitionReader 讀取。DefaultListableBeanFactory 繼承了 AbstractAutoWireCapableBeanFactory 並實現了 ConfigurableListableBeanFactory 以及 BeanDefinitionRegistry 介面。

容器載入相關類圖

從該類圖我們可以清晰瞭解 DefaultListableBeanFactory 的脈絡,我們先簡單瞭解一下各個類的作用。

  • AliasRegistry:定義對 alias 的簡單增刪改操作
  • SimpleAliasRegistry:主要使用 map 作為 alias 快取,並對介面 AliasRegistry 進行實現
  • SingletonBeanRegistry:定義對單例的註冊和獲取
  • BeanFactory:定義獲取 bean 和 bean 的各種屬性
  • DefaultSingletonBeanRegistry:對介面 SingletonBeanRegistry 的各個函式實現
  • HierarchicalBeanFactory:繼承 BeanFactory,在 BeanFactory 定義的功能基礎上增加了對 parentFactory 的支援
  • BeanDefinitionRegistry:定義對 BeanDefinition 的各種增刪改操作
  • FactoryBeanRegistrySupport:在 DefaultSingletonBeanRegistry 基礎上增加了對 FactoryBean 的特殊處理功能
  • ConfigurableBeanFactory:提供配置 Factory 的各種方法
  • ListableBeanFactory:根據各種條件獲取 bean 的配置清單
  • AbstractBeanFactory:綜合了 FactoryBeanRegistrySupport、ConfigurableBeanFactory 的功能
  • AutowireCapableBeanFactory:提供建立 bean、自動注入、初始化以及應用 bean 的後處理器
  • AbstractAutowireCapableBeanFactory:綜合 AbstractBeanFactory 功能並對介面 AutowireCapableBeanFactory 進行實現
  • ConfigurableListableBeanFactory:BeanFactory 配置清單,指定忽略型別及介面等
  • DefaultListableBeanFactory:綜合上面所有功能,主要對 bean 註冊後的處理
XmlBeanFactory 繼承 DefaultListableBeanFactory 進行了擴充套件,主要用於從 XML 中讀取 BeanDefinition,對於註冊和獲取 bean 都是使用從父類 DefaultListableBeanFactory 繼承的方法中實現,與父類不同的是 XmlBeanFactory 增加了 XmlBeanDefinitionReader 型別的 reader 屬性,進行對資原始檔的讀取和註冊。

2.XmlBeanDefinitionReader

XML 配置檔案的讀取是 Spring 中重要的功能,我們可以從 XmlBeanDefinitionReader 中梳理一下資原始檔讀取、解析及註冊的大致脈絡。

配置檔案讀取相關類圖

我們首先看一下各個類的功能:

  • ResourceLoader:定義資源載入器,主要應用於根據給定的資原始檔地址返回對應的 Resource
  • BeanDefinitionReader:主要定義資原始檔讀取並轉換為 BeanDefinition 的各個功能
  • EnvironmentCapable:定義獲取 Environment 方法
  • DocumentLoader:定義從資原始檔載入到轉換為 Document 的功能
  • AbstractBeanDefinitionReader:對 EnvironmentCapable、BeanDefinitionReader 類定義的方法進行實現
  • BeanDefinitionDocumentReader:定義讀取 Document 並註冊 BeanDefinition 功能
  • BeanDefinitionParserDelegate:定義解析 Element 的各種方法

經過上面的分析,我們可以梳理出 XML 配置檔案讀取的大概流程。

  1. 通過繼承自 AbstractBeanDefinitionReader 中的方法,來使用 ResourLoader 將資原始檔路徑轉換為對應的 Resource 檔案
  2. 通過 DocumentLoader 對 Resource 檔案進行轉換,將 Resource 檔案轉換為 Document 檔案
  3. 通過實現介面 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 類對 Document 進行解析,並使用 BeanDefinitionParserDelegate 對 Element 進行解析

容器的基礎 XmlBeanFactory

在有了對 Spring 容器的大致瞭解後,我們接下來分析下面程式碼的實現。

 final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));

XmlBeanFactory初始化時序圖

通過時序圖我們可以看到首先呼叫了 ClassPathResource 的建構函式來構造 Resource 資原始檔的例項物件,之後的資源處理就可以用 Resource 提供的服務來操作了,之後就可以進行對 XmlBeanFactory 進行初始化了,接下來我們看一下 Resource 資源是如何封裝的。

配置檔案封裝

首先讀取檔案是通過 ClassPathResource 進行封裝的,比如new ClassPathResource("beanFactoryTest.xml"),那麼 ClassPathResource 完成了什麼功能?

在 Java 中,將不同來源的資源抽象為 URL,然後註冊不同的 handler(URLStreamHandler)來處理不同資源的讀取邏輯,但是 URL 沒有預設定義相對 Classpath 或 ServletContext 等資源的 handler,雖然可以通過註冊自己的 URLStreamHandler 來解析特定的 URL 字首協議,然而這需要了解 URL 的實現機制,而且 URL 也沒有提供基本的方法(比如資源是否可讀、是否存在等)。因而在 Spring 中對其內部使用到的資源實現了自己的抽象結構,Resource 介面封裝底層資源。

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}
public interface Resource extends InputStreamSource {

   boolean exists();


   default boolean isReadable() {
      return exists();
   }


   default boolean isOpen() {
      return false;
   }

   default boolean isFile() {
      return false;
   }


   URL getURL() throws IOException;

   URI getURI() throws IOException;

   File getFile() throws IOException;

   default ReadableByteChannel readableChannel() throws IOException {
      return Channels.newChannel(getInputStream());
   }

   long contentLength() throws IOException;

   long lastModified() throws IOException;

   Resource createRelative(String relativePath) throws IOException;

   @Nullable
   String getFilename();

   String getDescription();

}

InputStreamSource 中只有一個方法 getInputStream(),返回一個新的 InputStream 物件。

Resource 介面抽象了 Spring 內部使用到的底層資源,File、URL、Classpath 等。定義了 4 個判斷資源狀態的方法:exists()、isReadable()、isOpen()、isFile(),另外,Resource 介面也提供了不同資源到 URL、URI、File 型別的轉換。

createRelative()方法可以基於當前資源建立一個相對資源的方法。

getDescription()方法用來在錯誤處理中列印資訊。

大致瞭解了 Spring 中將配置檔案封裝為 Resource 型別的例項方法後,我們繼續看 XmlBeanFactory 的初始化過程,我們這裡通過使用 Resource 例項作為建構函式引數的方法。

public XmlBeanFactory(Resource resource) throws BeansException {
   this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

上面的方法中this.reader.loadBeanDefinitions(resource);就是資源載入的真正實現,也是分析的重點之一

我們上面的時序圖 3.1 就是這裡完成的。在呼叫到該方法之前,我們還要呼叫父類構造器進行初始化。

我們直接跟蹤到父類 AbstractAutowireCapableBeanFactory 的建構函式中:

public AbstractAutowireCapableBeanFactory() {
   super();
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
}

ignoreDependencyInterface 的主要功能是忽略給定介面的自動裝配功能。為什麼要這樣做?

比如:當 A 中有屬性 B,當 Spring 在獲取 A 的 bean 的時候如果 B 還沒初始化,那麼 Spring 會自動初始化 B,但是在某些情況下,B 不會被初始化,其中一個情況就是 B 實現了 BeanNameAware 介面。

Spring 中這樣介紹的:自動裝配時忽略給定的依賴介面,典型應用就是通過其他方式解析 Application 上下文註冊依賴,類似於 BeanFactory 通過 BeanFactoryAware 進行注入或者 ApplicationContext 通過 ApplicationContextAware 進行注入。

載入 Bean

回到剛才,我們在 XmlBeanFactory 建構函式中呼叫了this.reader.loadBeanDefinitions(resource);這句程式碼就是整個資源載入的入口,我們看一下這個方法的時序圖。

loadBeanDefinitions載入時序圖

我們嘗試梳理一下處理過程:

  1. 使用 EncodedResource 類對引數 Resource 資原始檔進行封裝
  2. 從 Resource 中獲取對應的 InputStream,隨後構造 InputSource
  3. 隨後使用構造的 InputSource 例項和 Resource 例項繼續呼叫 doLoadBeanDefinitions 方法

接下來看一下 loadBeanDefinitions 方法具體實現過程。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}

首先我們看一下 EncodedResource 的作用是什麼,通過名字可以看出來是對資原始檔進行編碼處理,主要邏輯就在其中的getReader()方法中,如果設定了編碼屬性,Spring 會使用相應的編碼作為輸入流的編碼。

public Reader getReader() throws IOException {
   if (this.charset != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.charset);
   }
   else if (this.encoding != null) {
      return new InputStreamReader(this.resource.getInputStream(), this.encoding);
   }
   else {
      return new InputStreamReader(this.resource.getInputStream());
   }
}

該方法構造了一個 InputStreamReader。當構造完 EncodedResource 之後,呼叫了 loadBeanDefinitions 過載方法。

該方法內部就是真正的資料準備階段了。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isTraceEnabled()) {
      logger.trace("Loading XML bean definitions from " + encodedResource);
   }
        //resourcesCurrentlyBeingLoaded是一個ThreadLocal,裡面存放著Resource類的set集合
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   if (currentResources == null) {
      currentResources = new HashSet<>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
   }
   //如果set中已有這個元素則返回false並丟擲異常
   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }
   try {
      //從encodedResource中獲取已經封裝的Resource物件並再次從Resource中獲取InputStream
      InputStream inputStream = encodedResource.getResource().getInputStream();
      try {
         //準備解析xml檔案,全路徑為org.xml.sax.InputSource
         InputSource inputSource = new InputSource(inputStream);
         //設定編碼集
         if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
         }
         return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
      }
      finally {
         inputStream.close();
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
   }
   finally {
       //資源載入完畢,移除該Resource
      currentResources.remove(encodedResource);
      //如果沒有其他資源了,則remove
      if (currentResources.isEmpty()) {
         this.resourcesCurrentlyBeingLoaded.remove();
      }
   }
}

該方法首先將傳入的 Resource 引數進行封裝,目的是為了考慮到 Resource 可能存在編碼要求的情況,其次,通過 SAX 讀取 XML 檔案的方式來建立 InputSource 物件,最後將引數傳入到核心處理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {

   try {
      Document doc = doLoadDocument(inputSource, resource);
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) {
         logger.debug("Loaded " + count + " bean definitions from " + resource);
      }
      return count;
   }
   catch (BeanDefinitionStoreException ex) {
      throw ex;
   }
   // ...省略catch
}

該方法主要做三件事:

  • 獲取對 XML 檔案的驗證模式
  • 載入 XML 檔案,得到對應的 Document
  • 根據返回的 Document 註冊 Bean 資訊

我們一步步看,從 XML 驗證模式開始。

獲取 XML 的驗證模式

比較常用的驗證 XML 正確性有兩種:DTD 和 XSD。

DTD(Document Type Definition)即文件型別定義,是一種 XML 約束模式語言,是 XML 檔案的驗證機制

DTD 即文件型別定義,是一種 XML 約束模式語言,是 XML 檔案的驗證機制,屬於 XML 檔案組成的一部分。
DTD 是一種保證 XML 文件格式正確的有效方法,可以通過比較 XML 文件和 DTD 檔案來看文件是否符合規範,元素和標籤使用是否正確。 一個 DTD 文件包含:元素的定義規則,元素間關係的定義規則,元素可使用的屬性,可使用的實體或符號規則。

DTD 和 XSD 相比:DTD 是使用非 XML 語法編寫的。
DTD 不可擴充套件,不支援名稱空間,只提供非常有限的資料型別。

XSD(XML Schemas Definition),即 XML Schema 語言,針對 DTD 的缺陷有 W3C 在 2001 年推出。XML Schema 本身就是一個 XML 文件,使用的是 XML 語法,因此可以很方便地解析 XSD 文件。

相對於 DTD, XSD 具有如下優勢:

  • XML Schema 基於 XML,沒有專門的語法
  • XML Schema 可以像其他 XML 檔案一樣解析和處理
  • XML Schema 相比於 DTD 提供了更豐富的資料型別
  • XML Schema 提供可擴充套件的資料模型
  • XML Schema 支援綜合名稱空間
  • XML Schema 支援屬性組
在 Spring 原始碼中,基於 XML 檔案配置 Bean 的驗證模式,一般情況下是 XSD 模式。

XSD模式

驗證模式的讀取

protected int getValidationModeForResource(Resource resource) {
   int validationModeToUse = getValidationMode();
    //如果手動指定了驗證模式則使用指定的驗證模式
   if (validationModeToUse != VALIDATION_AUTO) {
      return validationModeToUse;
   }
   // 如果未指定則使用自動檢測
   int detectedMode = detectValidationMode(resource);
   if (detectedMode != VALIDATION_AUTO) {
      return detectedMode;
   }
   return VALIDATION_XSD;
}

Spring 檢測驗證模式就是通過判斷是否包含 DOCTYPE,包含就是 DTD,否則是 XSD。

獲取 Document

經過驗證模式準備後,就可以進行 Document 載入了,這裡的 documentLoader 是一個介面,真正實現是下面的 DefaultDocumentLoader。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
      //建立文件構建器工廠物件,並初始化一些屬性
    //如果驗證模式為XSD,那麼強制支援XML名稱空間,並加上schema屬性
   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isTraceEnabled()) {
      logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   //按照XML文件解析給定inputSource的內容,然後返回一個新的DOM物件
   return builder.parse(inputSource);
}

這段程式碼主要建立了一個 DocumentBuilderFactory 例項,再通過 DocumentBuilderFactory 建立了一個 DocumentBuilder,最後解析 inputSource 返回 Document 物件。

解析及註冊 BeanDefinitions

我們再回到 doLoadBeanDefinitions 方法,拿到 Document 物件後,我們就可以註冊 bean 物件,呼叫 registerBeanDefinitions 方法。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   //例項化BeanDefinitionDocumentReader
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   //記錄統計前BeanDefinition的載入個數
   int countBefore = getRegistry().getBeanDefinitionCount();
   //載入及註冊bean(關鍵)
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   //記錄本次載入個數
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

呼叫 registerBeanDefinitions 方法,選擇實現類為DeDefaultBeanDefinitionDocumentReader

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   //拿到了xml文件物件的根元素 並呼叫該方法
   doRegisterBeanDefinitions(doc.getDocumentElement());
}

doRegisterBeanDefinitions 開始真正地解析。

protected void doRegisterBeanDefinitions(Element root) {
    // 任何被巢狀的<beans>元素都會導致此方法的遞迴。為了正確的傳播和儲存<beans>的預設屬性、
    // 保持當前(父)代理的跟蹤,它可能為null
    // 為了能夠回退,新的(子)代理具有父的引用,最終會重置this.delegate回到它的初始(父)引用。
    // 這個行為模擬了一堆代理,但實際上並不需要一個代理
   BeanDefinitionParserDelegate parent = this.delegate;
   //程式碼(1)
   this.delegate = createDelegate(getReaderContext(), root, parent);
      //預設名稱空間是"http://www.springframework.org/schema/beans"
   if (this.delegate.isDefaultNamespace(root)) {
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
     //在xml配置檔案中對profile的設定 區分是生產環境還是線上環境
      if (StringUtils.hasText(profileSpec)) {
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }
   //解析前處理,留給子類實現
   preProcessXml(root);
   //生成BeanDefinition,並註冊在工廠中,程式碼(2)
   parseBeanDefinitions(root, this.delegate);
   //解析後處理,留給子類實現
   postProcessXml(root);

   this.delegate = parent;
}

我們先看一下程式碼(1)的方法,該方法建立了一個 BeanDefinitionParserDelegate 物件,該物件是對 XML 中屬性值解析的委派。

protected BeanDefinitionParserDelegate createDelegate(
      XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {

   BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
   delegate.initDefaults(root, parentDelegate);
   return delegate;
}

我們看下 BeanDefinitionParserDelegate 類的常量。

public class BeanDefinitionParserDelegate {

   public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

   public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; ";

   /**
    * Value of a T/F attribute that represents true.
    * Anything else represents false. Case seNsItive.
    */
   public static final String TRUE_VALUE = "true";

   public static final String FALSE_VALUE = "false";

   public static final String DEFAULT_VALUE = "default";

   public static final String DESCRIPTION_ELEMENT = "description";

   public static final String AUTOWIRE_NO_VALUE = "no";

   public static final String AUTOWIRE_BY_NAME_VALUE = "byName";

   public static final String AUTOWIRE_BY_TYPE_VALUE = "byType";

   public static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor";

   public static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect";

   public static final String NAME_ATTRIBUTE = "name";

   public static final String BEAN_ELEMENT = "bean";

   public static final String META_ELEMENT = "meta";

   public static final String ID_ATTRIBUTE = "id";

   public static final String PARENT_ATTRIBUTE = "parent";

   //...
}

我們發現 Spring 配置檔案的屬性全都在這裡 現在我們知道 BeanDefinitionParserDelegate 物件確實是來對 XML 配置檔案的解析後,繼續回到 createDelegate 方法,建立了 BeanDefinitionParserDelegate 物件後,還執行了 initDefaults 方法,來初始化一些預設值。

解析並註冊 BeanDefinition

現在我們回到程式碼(2),進入 parseBeanDefinitions 方法。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   //對beans的處理,預設名稱空間是"http://www.springframework.org/schema/beans"
   if (delegate.isDefaultNamespace(root)) {
     //獲取根元素下的子Node
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (node instanceof Element) {
           //拿到了<beans>下的子標籤
            Element ele = (Element) node;
            if (delegate.isDefaultNamespace(ele)) {
               //如果該標籤屬於beans的名稱空間,則進入這個方法
               //xmlns="http://www.springframework.org/schema/beans"
               parseDefaultElement(ele, delegate);
            }
            else {
               //如果該標籤屬於其他的名稱空間比如:context,aop等
               //xmlns:aop="http://www.springframework.org/schema/aop"
               //xmlns:context="http://www.springframework.org/schema/context"
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

在 Spring 中的 XML 分為兩大類,一種是預設的如下:

<bean id="test" class="cn.jack.Test"/>

另一種就是自定義的:

<tx:annotation-driven/>

如果是自定義實現的話,則需要使用者實現自定義配置,如果根節點或者節點是使用預設命名的話則使用 parseDefaultElement 進行解析,否則使用 delegate.parseCustomElement 方法對自定義名稱空間進行解析。

名稱空間對比

而判斷是預設命名還是自定義名稱空間則使用 isDefaultNamespace 方法中的 node.getNamespaceURI()獲取名稱空間,隨後與固定的名稱空間http://www.springframework.org/schema/beans進行對比,不一致則為自定義名稱空間。

對於預設標籤解析與自定義標籤解析則在下一篇文章中。

相關文章