Spring3.2 中 Bean 定義之基於 XML 配置方式的源碼解析

Spring3.2 中 Bean 定義之基於 XML 配置方式的源碼解析

本文簡要介紹了基於 Spring 的 web project 的啓動流程,詳細分析了 Spring 框架將開發人員基於 XML 定義的 Bean 信息轉換爲 Spring 框架的 Bean Definition 對象的處理過程,向讀者展現了 Spring 框架的奧妙之處,能夠加深開發人員對 Spring 框架的理解。java

秦 天傑, 軟件工程師, IBM Chinaweb

2013 年 9 月 02 日spring

  • +內容

Spring 做爲成熟的開源框架,已被開發人員普遍使用於平常開發中。Spring 自帶的參考文檔給開發人員提供了詳細的使用介紹。而做爲開源框架的 Spring 源碼,更爲開發人員提供了許多優雅的設計思想和具體實現參考。app

文章開始前,咱們定義一個名詞:Bean Definition:即 Bean 定義,對應於 Spring 框架對一個 Bean 的定義,包括各類不一樣的屬性參數,每一個 Bean 都有一個或多個相關的 Bean Definition。 爲了文章的可讀性,在此我使用斜體表示,不將其翻譯。框架

與 Java 程序中一個對象的執行大概經歷定義、實例化和使用等階段類似。Spring 容器中對象更加明確的經歷了定義、實例化和使用等階段:ide

  • 對象定義: Spring 容器啓動時,會根據開發人員對 Bean(對象)的定義,統一解析這些 Beans 到 Bean Definition容器 (ConcurrentHashMap<String, BeanDefinition>beanDefinitionMap) 中。同時 Spring 也會解析一些容器啓動所必須的和一些默認行爲的 Beans 到Bean Definition容器中。
  • 對象實例化: Spring 容器根據必定的規則,將 beanDefinitionMap 中的每一個 Bean Definition實例化爲具體的 Java 對象,同時會將 Singleton 類型的對象緩存到 bean 容器 (ConcurrentHashMap<String, Object> singletonObjects) 中。
  • 對象使用: 前兩步驟爲 Spring 容器啓動時執行,該步驟爲應用程序在執行相關業務邏輯時,會到 Spring 容器中找出(Single 類型)或者實例化(如 Prototype 類型)相關對象,供業務邏輯使用。

本文主要分析 Spring IoC 容器解析基於 XML 配置方式的 Bean Definition的源碼實現,給開發人員提供一個知其然,並知其因此然的途徑。函數

Spring 簡介及 Bean Definition 配置方式

Spring 是 2003 年興起的一個輕量級 Java 開發框架,通過十多年的不斷積累和演進,現在已成爲功能相同或類似解決方案最爲流行的開源框架。Spring IoC 改變了傳統的對象定義和使用方式,Spring AOP 爲開發人員提供了一種簡單但功能強大的面向切面的編程方式,Spring MVC 簡化了 WEB 開發的流程,使得開發人員更能專一於業務邏輯代碼的開發。 Spring 能夠用在各類類型的應用開發上。對於 Web Project, 開發人員只要在 web.xml 加入下列代碼,並將相關 Spring 依賴文件引入就能夠在開發中使用 Spring。

清單 1. Web.xml 引入 Spring
 <listener>				 
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
 </listener>

這是一個典型的 ServletContextListener,Servlet 容器(如 Tomcat 等)在啓動時會找到 ContextLoaderListener 並執行其 contextInitialized(ServletContextEvent event) 方法。從這裏開始,Spring 將會進行 Bean Definition的解析、Bean Processors 設置和處理、Beans 實例化等工做。從而將程序會用到的 Java 對象定義並根據該定義建立好,提供給開發人員去使用。 這裏 Spring 首先須要處理的就是 Bean 的定義。通過不斷的發展和演化,Bean 的定義方式有:

  • 基於 XML 文件的配置方式。
  • 基於 Annotation 的配置方式。
  • 基於 Java Code 的配置方式。
  • 用戶自定義的配置方式。
 

基於 XML 配置 Bean Definition 的源碼解讀

XML 配置 root WebApplicationContext

前文介紹,Servlet 容器啓動時若是 web.xml 配置了 ContextLoaderListener,則會調用該對象的初始化方法。根據 Java 語法規定,ContextLoaderListener 的父類 ContextLoader 有一段 static 的代碼會更早被執行,這段代碼配置了 XML 默認使用的 Context 爲org.springframework.web.context.WebApplicationContext = org.springframework.web.context.support.XmlWebApplicationContext 根據該定義,若是開發人員沒有從 web.xml 指定 contextClass 參數,則默認使用 XmlWebApplicationContext 做爲 root WebApplicationContext 工具類。

XML 命名空間及 XML 配置文件解析

Spring 採用 XML 命名空間的方式,爲框架的擴展提供了極大的方便。

清單 2. XML 配置文件的命名空間定義
 <?xml version="1.0" encoding="UTF-8"?> 
 <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/context 
 http://www.springframework.org/schema/context/spring-context-3.2.xsd 
 http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans-3.2.xsd>

清單 2 展現在 XML 配置文件中引入命名空間的配置範例。

圖 1. XML 命名空間及其 handler 定義(查看大圖
圖 1. XML 命名空間及其 handler 定義

展開 Spring 的依賴 jar 文件,能夠看到 Spring 各個模塊對應的 jar 包都包含一個 META-INF 目錄,若是使用了命名空間,則該目錄包含了 spring.schemas 和 spring.handlers 兩個文件,如圖 1 所示。而後 Spring 代碼經過 Properties 工具類掃描 project 的 classpath 以及其使用的全部依賴包中 META-INF 目錄來獲得對應參數值供後續使用。

PropertiesLoaderUtils.loadAllProperties(「META-INF/spring.schemas」, this.classLoader); PropertiesLoaderUtils.loadAllProperties(「META-INF/spring.handlers」, this.classLoader);

清單 3. JAXP 方式解析 XML 文件爲 Java 對象
 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 
 throws BeanDefinitionStoreException { 
 try{ 
   int validationMode = getValidationModeForResource(resource); 
 // 使用 JAXP 方式,將 XML 配置文件解析爲 Java 對象。
 Document doc = this.documentLoader.loadDocument(inputSource,  
 getEntityResolver(),this.errorHandler,  
 validationMode, isNamespaceAware()); 
  return registerBeanDefinitions(doc, resource); 
  }

清單 3 表示 Spring 源碼會建立一些必須的屬性或對象,讀入配置文件(一般爲 XML 文件),使用 JAXP 方式將 XML 元素轉換爲 Java 對象(Document doc)。 這樣 Spring 對 XML 配置文件的處理,均可以看做對 Java 對象的處理。

圖 2. XML 配置文件解析預處理順序圖(查看大圖
圖 2. XML 配置文件解析預處理順序圖

圖 2 展現 Spring 框架如何根據 XML 配置文件,建立輔助類,從而實現對用戶自定義的 Bean 解析的過程。詳細分析以下:

  1. Spring 容器在 Servlet 容器啓動後,通過一系列的配置和處理到達以下步驟;
  2. 調用 AbstractApplicationContext 對象的 refreshBeanFactory(); 方法。在這個方法裏,將會完成後面全部解析處理。
  3. 步驟 2 首先建立一個 DefaultListableBeanFactory 對象,該對象的類圖如圖 3 所示。這是 Spring 框架的核心類,Spring 的 Bean 解析和實例化以及後續的使用都跟該類有很是密切的關係。從類圖右上角部分可知,該類實現了 BeanFactory 及其幾個子接口,這裏定義了 DefaultListableBeanFactory 具備的業務接口(如 getBean(...) 等)。而圖的左邊的類圖展現了 DefaultListableBeanFactory 類的具體業務實現。
圖 3. DefaultListableBeanFactory 類圖(查看大圖
圖 3. DefaultListableBeanFactory 類圖
  1. 調用 XmlWebApplicationContext 對象的 loadBeanDefinitions 方法,其參數爲步驟 3 建立的 beanFactory. 這個方法定義了 XmlBeanDefinitionReader 對象 beanDefinitionReader 並設置了其屬性 ( 如 DocumentLoader,EntityResolver 等 ) 用於 XML 文件的解析。XmlWebApplicationContext 對象還負責設置 XML 配置文件名,默認爲 /WEB-INF/applicationContext.xml。若是是開發人員定義的配置文件,則可能爲多個。
  2. 該步驟根據步驟 4 定義的配置文件名(一個或多個),調用 beanDefinitionReader 的 loadBeanDefinitions 方法循環解析每一個配置文件,傳入的參數爲配置文件名。
  3. 步驟 5 的 beanDefinitionReader 會經過 JAXP 方式解析 XML 配置文件爲 Document 對象 doc,Spring 框架對 XML 配置文件的處理即轉化爲對 Java 對象(Document) 的處理。
  4. 步驟 4 中 beanDefinitionReader 會建立兩個對象:DefaultBeanDefinitionDocumentReader 對象 documentReader 和 XmlReaderContext 對象 readerContext. DefaultBeanDefinitionDocumentReader 的屬性如清單 4:
清單 4. DocumentReader 屬性清單
public class DefaultBeanDefinitionDocumentReader 
 implements BeanDefinitionDocumentReader { 
 public static final String BEAN_ELEMENT=BeanDefinitionParserDelegate.BEAN_ELEMENT; 
 public static final String NESTED_BEANS_ELEMENT = "beans"; 
 public static final String ALIAS_ELEMENT = "alias"; 
 public static final String NAME_ATTRIBUTE = "name"; 
 public static final String ALIAS_ATTRIBUTE = "alias"; 
 public static final String IMPORT_ELEMENT = "import"; 
 public static final String RESOURCE_ATTRIBUTE = "resource"; 
 /** @see org.springframework.context.annotation.Profile */ 
 public static final String PROFILE_ATTRIBUTE = "profile"; 
 protected final Log logger = LogFactory.getLog(getClass()); 
 private XmlReaderContext readerContext; 
 private Environment environment; 
 private BeanDefinitionParserDelegate delegate;

從上述清單以及 XML 配置對比可知,該對象定義了 <beans /> 元素可配置文件。其第 一個屬性即爲每一個 Bean 的配置元素 <bean />。

readerContext 對象定義了一些如 resource,eventListener 和 namespaceHandlerResolver 等上下文信息。經過這些屬性,Spring 框架得到須要解析的 XML 文件,命名空間解析輔助類以及特定事件的監聽器等。

  1. 以步驟 6 和 7 建立的 doc 和 readerContext 爲參數,調用 documentReader 對象的 registerBeanDefinitions 方法。
  2. 步驟 8 會建立一個工具類 BeanDefinitionParserDelegate ,其部分屬性如清單 5。
清單 5. BeanDefinitionParserDelegate 屬性清單
 public class BeanDefinitionParserDelegate { 
       ... 
	 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 DEPENDENCY_CHECK_ALL_ATTRIBUTE_VALUE = "all"; 
	 public static final String DEPENDENCY_CHECK_SIMPLE_ATTRIBUTE_VALUE = "simple"; 
	 public static final String DEPENDENCY_CHECK_OBJECTS_ATTRIBUTE_VALUE = "objects"; 
	 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"; 
	 public static final String CLASS_ATTRIBUTE = "class"; 
	 public static final String ABSTRACT_ATTRIBUTE = "abstract"; 
	 public static final String SCOPE_ATTRIBUTE = "scope"; 
	 public static final String SINGLETON_ATTRIBUTE = "singleton"; 
	 public static final String LAZY_INIT_ATTRIBUTE = "lazy-init"; 
	 public static final String AUTOWIRE_ATTRIBUTE = "autowire"; 
	 public static final String AUTOWIRE_CANDIDATE_ATTRIBUTE = "autowire-candidate"; 
	 public static final String PRIMARY_ATTRIBUTE = "primary"; 
	 public static final String DEPENDENCY_CHECK_ATTRIBUTE = "dependency-check"; 
	 public static final String DEPENDS_ON_ATTRIBUTE = "depends-on"; 
	 public static final String INIT_METHOD_ATTRIBUTE = "init-method"; 
	 public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; 
	 public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method"; 
	 public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean"; 
	 public static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg"; 
	 public static final String INDEX_ATTRIBUTE = "index"; 
	 public static final String TYPE_ATTRIBUTE = "type"; 
	 public static final String VALUE_TYPE_ATTRIBUTE = "value-type"; 
	 public static final String KEY_TYPE_ATTRIBUTE = "key-type"; 
	 public static final String PROPERTY_ELEMENT = "property"; 
	 public static final String REF_ATTRIBUTE = "ref"; 
	 public static final String VALUE_ATTRIBUTE = "value"; 
	 public static final String LOOKUP_METHOD_ELEMENT = "lookup-method"; 
	 public static final String REPLACED_METHOD_ELEMENT = "replaced-method"; 
	 public static final String REPLACER_ATTRIBUTE = "replacer"; 
	 public static final String ARG_TYPE_ELEMENT = "arg-type"; 
	 public static final String REF_ELEMENT = "ref"; 
	 ... 
	 public static final String DEFAULT_LAZY_INIT_ATTRIBUTE = "default-lazy-init"; 
	 public static final String DEFAULT_MERGE_ATTRIBUTE = "default-merge"; 
	 public static final String DEFAULT_AUTOWIRE_ATTRIBUTE = "default-autowire"; 
	 public static final String DEFAULT_DEPENDENCY_CHECK_ATTRIBUTE = 
"default-dependency-check"; 
	 public static final String DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE = 
"default-autowire-candidates"; 
	 public static final String DEFAULT_INIT_METHOD_ATTRIBUTE = 
"default-init-method"; 
	 public static final String DEFAULT_DESTROY_METHOD_ATTRIBUTE = 
"default-destroy-method"; 
       ... 
	 private final XmlReaderContext readerContext; 
	 private final DocumentDefaultsDefinition defaults = 
 new DocumentDefaultsDefinition(); 
	 private final ParseState parseState = new ParseState(); 
	 private Environment environment;

由上述清單可見,這就是咱們熟悉的 <bean /> 元素須要配置的屬性。同時也包括 <beans /> 元素的全局屬性。

圖 2 所示,該對象會根據讀入 XML 文件的屬性及其 parent 屬性,給對象自身屬性設置一個默認值。

  1. 順序圖的最後一步描述如何將 XML 配置文件中每一個 <bean /> 元素解析到 Bean Definition對象,具體解析步驟將在下面繼續分析。

XML 默認命名空間 bean 元素解析

上一步描述了 Spring 如何讀入一個 XML 配置文件,並設置一系列工具類,經過 JAXB 方法將 XML 文件轉換爲 Java 對 Document 對象的處理過程。本節具體描述 Spring 框架是如何解析 XML 配置元素爲一個 Bean Definition對象的處理過程。

清單 6. 根據命名空間的 Bean 解析處理流程
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate){ 
 // 若是 XML 根元素爲默認命名空間 (xmlns="http://www.springframework.org/schema/beans"),
 // 則直接解析 XML 配置文件的各元素。
 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; 
			 // 若是 XML 的元素是默認命名空間,調用 parseDefaultElement 方法解析 . 
			 if (delegate.isDefaultNamespace(ele)) { 
				 parseDefaultElement(ele, delegate); 
			 } 
			 else { 	 
			 // 若是 XML 的元素不是默認命名空間,如使用 P,content 前綴等,
			 // 調用 delegate 的 parseCustomElement 方法解析 . 
				 delegate.parseCustomElement(ele); 
			 } 
		 } 
	 } 
 } 
 else {  // 若是 XML 根元素爲非默認命名空間,直接調用 delegate 的 parserCustomElement 
	 // 處理 XML 配置文件各元素。
	 delegate.parseCustomElement(root); 
 } 
 }

清單 6 根據將要解析節點的命名空間選擇對應的解析函數。若是爲默認命名空間,則使用當前 reader 的 parseDefaultElement() 函數,傳入參數爲該節點元素 (ele) 和前文建立的 delegate 對象。反之,則使用前文建立的 delegate 對象進行解析。 根據 Spring 相關文檔及源碼可知,ele 元素可能爲 import、alias、beans 和 bean 其中一種,爲簡單起見,詳細解讀 bean 元素源碼以下。

清單 7. 一個典型的 Bean 配置示例
 <bean id="xmlBeanSample" class="com.colorcc.spring.sample.XmlBeanSample"
 p:xmlSimpleBeanSample-ref="xmlSimpleBeanSample"> 
 <property name="name"value="XmlBeanSample.Jack"/> 
 <property name="xsbs"ref="xmlSimpleBeanSample"/> 
 </bean>

一個典型的 <bean /> 元素配置如清單 7 所示。其包括屬性和子節點元素信息。屬性包括如 id、name、class 和 scope 等,開發人員配置這些屬性值後,Spring 容器在啓動的時候使用這些屬性值替換上一節步驟 9 的默認屬性值,從而獲得用戶定義的 bean 配置。p:xmlSimpleBeanSample-ref= "xmlSimpleBeanSample" 表示使用命名空間 "http://www.springframework.org/schema/p" 定義的一個屬性值。子節點 <property/> 也定義了 <bean/> 元素的一些屬性。其與屬性的區別在於屬性是 Spring 框架定義的、與生俱有的。而 <property/> 則能夠包含開發人員本身定義的任意修飾元素。

圖 4. 基於 XML 配置方式的 Bean 元素解析流程(查看大圖
圖 4. 基於 XML 配置方式的 Bean 元素解析流程

圖 4 爲 <bean /> 元素解析過程的順序圖,簡單起見,省略了順序圖部分元素。分析以下:

  1. 該順序圖接圖 2,首先調用 DefaultBeanDefinitionDocumentReader 的 parseDefaultElement 方法,傳入參數爲前文建立的 ele 和 delegate. 該方法會解析 ele 的 nodeName 屬性,並根據解析出來的屬性名,調用對應的方法。對於 <bean /> 元素,調用的方法爲 processBeanDefinition(ele, delegate);
  2. 步驟 1 的實際工做即爲執行 delegate.parseBeanDefinitionElement(ele) 方法,經過自身的方法調用,進入圖 4 中 (1) 處業務邏輯。該處會解析開發人員定義的 id 和 name 屬性,並根據解析值設置 beanName 和 aliases 值。其規則爲:若是 name 屬性存在,則將 name 值(可能爲多個)解析到名爲 aliases 的 List 中。若是 id 存在,則設置 beanName 值爲 id 值。反之,若是 name 值存在,則從 aliases 中移出第一個元素並設置爲 beanName 值。
  3. 緊接着根據步驟 2 獲得的 beanName 和 aliases 值,查詢容器 usedNames 是否已經有值,若是該值已經存在,則表示命名重複,拋出異常提示配置錯誤。反之將 beanName 和 aliases 中全部屬性名都加入 usedNames 容器中,繼續步驟 4。
  4. 根據上步獲得的 beanName, delegate 對象調用 parseBeanDefinitionElement 方法,其傳入參數爲 ele、beanName 和 containingBean。 其中最後一個參數默認爲 null. 這裏會根據 ele 對象,解析其 class 和 parent 屬性,並根據這兩個屬性值,調用 BeanDefinitionReaderUtils.createBeanDefinition(parentName,className, classLoader) 業務邏輯。這裏首先會 new 出一個 GenericBeanDefinition 對象 bd,其類圖如圖 5 所示。
圖 5. 經常使用 BeanDefinition 類圖
圖 5. 經常使用 BeanDefinition 類圖

由類圖可知,GenericBeanDefinition 是一個 AbstractBeanDefinition 對象,該對象在建立時會初始化一些默認屬性值如 scope="", singleton=true, lazyInit=false等。同時還經過構造函數的 setConstructorArgumentValues(cargs); 和 setPropertyValues(pvs); 方法初始化 Constructor 和 Property 屬性值的存放容器。而後根據傳入參數設置 bd 的 parentName 以及 beanClass 或者 beanClassName 值。最終,將獲得的 AbstractBeanDefinition 對象返回。

  1. 通過步驟 4 處理後,到達 delete 的 parseBeanDefinitionAttributes(ele,beanName,containingBean,bd) 的方法調用,如圖 4 的 (3) 處。這裏主要解析 ele 元素,設置 bd 對應的值,典型代碼如清單 8 所示。
清單 8. 根據 XML 配置給 GenericBeanDefinition 屬性賦值
 ... 
 // 根據配置文件設置 bd 的 abstract 屬性值
 if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { 
	 bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); 
 } 
 // 根據配置文件設置 bd 的 lazy-init 屬性值
 String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); 
 if (DEFAULT_VALUE.equals(lazyInit)) { 
	 lazyInit = this.defaults.getLazyInit(); 
 } 
 bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); 
 // 根據配置文件設置 bd 的 autowire 屬性值
 String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); 
 bd.setAutowireMode(getAutowireMode(autowire)); 
 // 根據配置文件設置 bd 的 dependency-check 屬性值
 String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE); 
 bd.setDependencyCheck(getDependencyCheck(dependencyCheck)); 
 ...
  1. 相似步驟 5,Spring 還會解析其餘配置元素如 ( 參考圖 4 的 (4) 處 ):
     // 解析 「meta」元素
     parseMetaElements(ele, bd); 
     // 解析 「lookup-method」元素
     parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); 
     // 解析 「replaced-method」元素
     parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); 
     // 解析 「constructor-arg」元素
     parseConstructorArgElements(ele, bd); 
     // 解析 「property」元素
     parsePropertyElements(ele, bd);  
     // 解析 「qualifier」元素
     parseQualifierElements(ele, bd);

    以 parsePropertyElements(ele,bd) 爲例,其解析步驟以下:

    • 6.1 首先根據 ele 元素 ( 對應一個 <bean /> 配置 ),循環讀取其全部 <property /> 子節點 notes. 注意該節點也包括 p 命名空間定義的屬性配置。
    • 6.2 解析每一個 note 的 name 屬性值 propertyName. 若是 propertyName 爲空,或者該 <bean /> 已經定義,則拋出錯誤,程序返回。不然繼續步驟 6.3。
    • 6.3 根據 ele.hasAttribute(name) 判斷 <property /> 是否有 ref 或者 value 屬性,若是兩者都有,則報錯。不然若是是 ref 屬性,則以 ref 屬性值爲參數建立 RuntimeBeanReference 對象。 若是有 value 屬性,則以 value 屬性值爲參數建立 TypedStringValue 對象。最終將 propertyName 和建立的對象構造爲 PropertyValue 對象,賦值到 beanDefinition 對應的 property 容器(propertyValues)中。

    注: 若是解析出來的 property 包含 「meta」元素,則循環解析該元素。

  2. 通過前述步驟處理,獲得一個 AbstractBeanDefinition 對象 beanDefinition. 若是該對象的 beanName 屬性不存在,則根據必定的規則,自動生成一個 beanName.。而後根據 beanName, beanDefinition 和 aliasesArray 建立一個 BeanDefinitionHolder 對象。
  3. 通過前述步驟,咱們已經獲得一個 BeanDefinitionHolder 對象,其包含 beanDefinition 屬性,描述了 bean 配置的屬性和子節點值。可是根據前文介紹,Spring 引入了命名空間。所以一個屬性可能配置形式如 p:name-ref="nameValue" 屬性,這是一個非默認命名空間的屬性。Spring 容器會作進一步處理(如圖 4 中兩個陰影部分 Loop 所示)。簡介以下:

    • 8.1 循環全部屬性節點,若是該節點不是默認命名空間,則根據命名空間,找到對應 的 NamespaceHandler 解析該屬性(如圖 1 所示)。
    • 8.2 若是屬性名以"-ref" 結尾,上節獲得的 Handler 根據 "-" 分割屬性爲屬性名和"-ref" 兩部分。對屬性名,若是包含一些特殊字符" -" ,會簡單處理,提供一個駝峯形式的屬性名 propertyName。對屬性值,封裝爲 RuntimeBeanReference 對象 rbr,最終將 propertyName 和 rbr 加入到 beanDefinition 的 property 容器。
    • 8.3 若是屬性不是以" -ref" 結尾,則認爲是屬性名,若有" -"字符,作相似 8.2 的處理。最終將 propertyName 及其 value 加入到 beanDefinition 的 property 容器。
    • 8.4 循環全部子節點,作相似 8.1 – 8.3 的處理。
  4. 這樣咱們就獲得了最終用開發人員配置的 bean 節點,其值存儲在 BeanDefinitionHolder 對象 bdHolder 中。將該 bdHolder 交由 DefaultBeanDefinitionDocumentReader 對象繼續處理。
  5. DefaultBeanDefinitionDocumentReader 根據自身屬性查找 BeanDefinitionRegistry 對象 registry 以便將獲得的 Bean 註冊到一個容器中,供 Spring 統一處理。根據代碼和圖 3 可知,該 registry 即爲前文描述的 DefaultListableBeanFactory 對象。根據 bdHolder 獲得 beanName 和 beanDefinition ,並將其做爲參數傳給 registry 的 registerBeanDefinition 方法(如圖 4 (6) 處所示)。
  6. 若是 beanDefinition 爲 AbstractBeanDefinition,則檢查其部分屬性的合法性。根據 beanName 查詢 registry 的 beanDefinitionMap 容器。若是找到對應元素,則根據 allowBeanDefinitionOverriding 屬性從新設置 beanDefinition(屬性值爲 true),或者拋出異常提示出錯(屬性值爲 false)。如未找到對應元素,則將 beanName 存入 beanDefinitionNames 容器,將 beanName 和 beanDefinition 以鍵值對形式存入 beanDefinitionMap 容器。最後將一些中間變量容器(如 singletonBeanNamesByType,factoryBeanInstanceCache 等)清值。
  7. 經過上述步驟,Spring 容器將開發人員定義的 bean 配置解析到 beanFactory 的 beanDefinitionMap 容器中。同時將全部 beanName 名也解析到 beanDefinitionNames 容器,方便後續繼續使用。Spring 容器所作的最後一個步驟就是發送一個 ReaderEventListener 事件。默認狀況下,該事件爲 EmptyReaderEventListener 對象的 componentRegistered 事件,這是一個空事件,沒有具體的業務邏輯。
 

小結

本文介紹了一個典型的基於 Spring 的 web project 的配置和啓動過程。詳細分析了 Spring 框架解析開發人員基於 XML 文件定義的 Bean 到 Spring 容器的 Bean Deifinition對象的處理過程。該方式是最原始也是使用最廣泛的一種方法,其優勢在於將配置集中在一塊兒(XML 配置文件中),且與 JAVA 代碼分離,方便管理。對於配置文件的改變,不須要從新編譯源代碼,極大的提升了開發效率。其缺點在於對大型基於 Spring 配置的項目,冗餘 XML 配置較多,增長了開發的工做量和維護成本。 後續文章會詳細分析 Spring 容器基於 Annotation 和 Java Code 方式解析 Bean Definition的處理流程,並簡要分析這些配置方式所適用的場景,但願對讀者有所幫助。

參考資料

學習

  • 訪問 JAXP 官方網址,有助於讀者瞭解使用 JAVA API 解析、驗證和查詢 XML 文件的相關知識,以及 Spring 如何使用 JAXP 方式處理用戶定義的 XML 文件。
  • 閱讀 Servlet 規範文檔可幫組讀者理解 Java Web Project 相關知識。
  • 參考 SpringSource 官方網址,Spring 是目前很是流行的企業級應用框架,您能夠在這裏瞭解更多關於 Spring 開源的詳細信息。
  • 下載並閱讀 Spring 源碼有助於讀者加深對本文的理解。
  • developerWorks Java 技術專區:這裏有數百篇關於 Java 編程各個方面的文章。

討論

  • 加入 developerWorks 中文社區。查看開發人員推進的博客、論壇、組和維基,並與其餘 developerWorks 用戶交流。
相關文章
相關標籤/搜索