博客 / 詳情

返回

Spring源碼之容器的基本實現

開篇

本文主要基於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進行對比,不一致則為自定義命名空間。

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

user avatar timliu_621f402489e7c 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.