Spring3.2 中 Bean 定义之基于 XML 配置方式的源码解析

Spring3.2 中 Bean 定义之基于 XML 配置方式的源码解析

本文简要介绍了基于 Spring 的 web project 的启动流程,详细分析了 Spring 框架将开发人员基于 XML 定义的 Bean 信息转换为 Spring 框架的 Bean Definition 对象的处理过程,向读者展示了 Spring 框架的奥妙之处,可以加深开发人员对 Spring 框架的理解。

秦 天杰, 软件工程师, IBM China

2013 年 9 月 02 日

  • +内容

Spring 作为成熟的开源框架,已被开发人员广泛使用于日常开发中。Spring 自带的参考文档给开发人员提供了详细的使用介绍。而作为开源框架的 Spring 源码,更为开发人员提供了许多优雅的设计思想和具体实现参考。

文章开始前,我们定义一个名词:Bean Definition:即 Bean 定义,对应于 Spring 框架对一个 Bean 的定义,包括各种不同的属性参数,每个 Bean 都有一个或多个相关的 Bean Definition。 为了文章的可读性,在此我使用斜体表示,不将其翻译。

与 Java 程序中一个对象的执行大概经历定义、实例化和使用等阶段相似。Spring 容器中对象更加明确的经历了定义、实例化和使用等阶段:

  • 对象定义: 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 用户交流。
posted @ 2015-06-17 12:03  wzhanke  阅读(255)  评论(0编辑  收藏  举报