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 定義(查看大圖)
展開 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 展現 Spring 框架如何根據 XML 配置文件,建立輔助類,從而實現對用戶自定義的 Bean 解析的過程。詳細分析以下:
- Spring 容器在 Servlet 容器啓動後,通過一系列的配置和處理到達以下步驟;
- 調用 AbstractApplicationContext 對象的 refreshBeanFactory(); 方法。在這個方法裏,將會完成後面全部解析處理。
- 步驟 2 首先建立一個 DefaultListableBeanFactory 對象,該對象的類圖如圖 3 所示。這是 Spring 框架的核心類,Spring 的 Bean 解析和實例化以及後續的使用都跟該類有很是密切的關係。從類圖右上角部分可知,該類實現了 BeanFactory 及其幾個子接口,這裏定義了 DefaultListableBeanFactory 具備的業務接口(如 getBean(...) 等)。而圖的左邊的類圖展現了 DefaultListableBeanFactory 類的具體業務實現。
圖 3. DefaultListableBeanFactory 類圖(查看大圖)
- 調用 XmlWebApplicationContext 對象的 loadBeanDefinitions 方法,其參數爲步驟 3 建立的 beanFactory. 這個方法定義了 XmlBeanDefinitionReader 對象 beanDefinitionReader 並設置了其屬性 ( 如 DocumentLoader,EntityResolver 等 ) 用於 XML 文件的解析。XmlWebApplicationContext 對象還負責設置 XML 配置文件名,默認爲 /WEB-INF/applicationContext.xml。若是是開發人員定義的配置文件,則可能爲多個。
- 該步驟根據步驟 4 定義的配置文件名(一個或多個),調用 beanDefinitionReader 的 loadBeanDefinitions 方法循環解析每一個配置文件,傳入的參數爲配置文件名。
- 步驟 5 的 beanDefinitionReader 會經過 JAXP 方式解析 XML 配置文件爲 Document 對象 doc,Spring 框架對 XML 配置文件的處理即轉化爲對 Java 對象(Document) 的處理。
- 步驟 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 文件,命名空間解析輔助類以及特定事件的監聽器等。
- 以步驟 6 和 7 建立的 doc 和 readerContext 爲參數,調用 documentReader 對象的 registerBeanDefinitions 方法。
- 步驟 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 屬性,給對象自身屬性設置一個默認值。
- 順序圖的最後一步描述如何將 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 爲 <bean /> 元素解析過程的順序圖,簡單起見,省略了順序圖部分元素。分析以下:
- 該順序圖接圖 2,首先調用 DefaultBeanDefinitionDocumentReader 的 parseDefaultElement 方法,傳入參數爲前文建立的 ele 和 delegate. 該方法會解析 ele 的 nodeName 屬性,並根據解析出來的屬性名,調用對應的方法。對於 <bean /> 元素,調用的方法爲 processBeanDefinition(ele, delegate);
- 步驟 1 的實際工做即爲執行 delegate.parseBeanDefinitionElement(ele) 方法,經過自身的方法調用,進入圖 4 中 (1) 處業務邏輯。該處會解析開發人員定義的 id 和 name 屬性,並根據解析值設置 beanName 和 aliases 值。其規則爲:若是 name 屬性存在,則將 name 值(可能爲多個)解析到名爲 aliases 的 List 中。若是 id 存在,則設置 beanName 值爲 id 值。反之,若是 name 值存在,則從 aliases 中移出第一個元素並設置爲 beanName 值。
- 緊接着根據步驟 2 獲得的 beanName 和 aliases 值,查詢容器 usedNames 是否已經有值,若是該值已經存在,則表示命名重複,拋出異常提示配置錯誤。反之將 beanName 和 aliases 中全部屬性名都加入 usedNames 容器中,繼續步驟 4。
- 根據上步獲得的 beanName, delegate 對象調用 parseBeanDefinitionElement 方法,其傳入參數爲 ele、beanName 和 containingBean。 其中最後一個參數默認爲 null. 這裏會根據 ele 對象,解析其 class 和 parent 屬性,並根據這兩個屬性值,調用 BeanDefinitionReaderUtils.createBeanDefinition(parentName,className, classLoader) 業務邏輯。這裏首先會 new 出一個 GenericBeanDefinition 對象 bd,其類圖如圖 5 所示。
圖 5. 經常使用 BeanDefinition 類圖
由類圖可知,GenericBeanDefinition 是一個 AbstractBeanDefinition 對象,該對象在建立時會初始化一些默認屬性值如 scope="", singleton=true, lazyInit=false等。同時還經過構造函數的 setConstructorArgumentValues(cargs); 和 setPropertyValues(pvs); 方法初始化 Constructor 和 Property 屬性值的存放容器。而後根據傳入參數設置 bd 的 parentName 以及 beanClass 或者 beanClassName 值。最終,將獲得的 AbstractBeanDefinition 對象返回。
- 通過步驟 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)); ...
- 相似步驟 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」元素,則循環解析該元素。
- 通過前述步驟處理,獲得一個 AbstractBeanDefinition 對象 beanDefinition. 若是該對象的 beanName 屬性不存在,則根據必定的規則,自動生成一個 beanName.。而後根據 beanName, beanDefinition 和 aliasesArray 建立一個 BeanDefinitionHolder 對象。
-
通過前述步驟,咱們已經獲得一個 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 的處理。
- 這樣咱們就獲得了最終用開發人員配置的 bean 節點,其值存儲在 BeanDefinitionHolder 對象 bdHolder 中。將該 bdHolder 交由 DefaultBeanDefinitionDocumentReader 對象繼續處理。
- DefaultBeanDefinitionDocumentReader 根據自身屬性查找 BeanDefinitionRegistry 對象 registry 以便將獲得的 Bean 註冊到一個容器中,供 Spring 統一處理。根據代碼和圖 3 可知,該 registry 即爲前文描述的 DefaultListableBeanFactory 對象。根據 bdHolder 獲得 beanName 和 beanDefinition ,並將其做爲參數傳給 registry 的 registerBeanDefinition 方法(如圖 4 (6) 處所示)。
- 若是 beanDefinition 爲 AbstractBeanDefinition,則檢查其部分屬性的合法性。根據 beanName 查詢 registry 的 beanDefinitionMap 容器。若是找到對應元素,則根據 allowBeanDefinitionOverriding 屬性從新設置 beanDefinition(屬性值爲 true),或者拋出異常提示出錯(屬性值爲 false)。如未找到對應元素,則將 beanName 存入 beanDefinitionNames 容器,將 beanName 和 beanDefinition 以鍵值對形式存入 beanDefinitionMap 容器。最後將一些中間變量容器(如 singletonBeanNamesByType,factoryBeanInstanceCache 等)清值。
- 經過上述步驟,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 用戶交流。