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

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

0 评论:

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

2013 年 9 月 02 日

  • 内容

在 IBM Bluemix 云平台上开发并部署您的下一个应用。

开始您的试用

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 定义(查看大图

展开 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 解析的过程。详细分析如下:

  1. Spring 容器在 Servlet 容器启动后,经过一系列的配置和处理到达如下步骤;
  2. 调用 AbstractApplicationContext 对象的 refreshBeanFactory(); 方法。在这个方法里,将会完成后面所有解析处理。
  3. 步骤 2 首先创建一个 DefaultListableBeanFactory 对象,该对象的类图如图 3 所示。这是 Spring 框架的核心类,Spring 的 Bean 解析和实例化以及后续的使用都跟该类有非常密切的关系。从类图右上角部分可知,该类实现了 BeanFactory 及其几个子接口,这里定义了 DefaultListableBeanFactory 具有的业务接口(如 getBean(...) 等)。而图的左边的类图展示了 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 为 <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 类图

由类图可知,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 用户交流。

Spring3.2 中 Bean 定义之基于 XML 配置方式的源码解析的更多相关文章

  1. struts_20_对Action中所有方法、某一个方法进行输入校验(基于XML配置方式实现输入校验)

    第01步:导包 第02步:配置web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app ...

  2. struts2视频学习笔记 22-23(基于XML配置方式实现对action的所有方法及部分方法进行校验)

    课时22 基于XML配置方式实现对action的所有方法进行校验   使用基于XML配置方式实现输入校验时,Action也需要继承ActionSupport,并且提供校验文件,校验文件和action类 ...

  3. 转载 - Struts2基于XML配置方式实现对action的所有方法进行输入校验

    出处:http://www.cnblogs.com/Laupaul/archive/2012/03/15/2398360.html http://www.blogjava.net/focusJ/arc ...

  4. Bean的基于XML配置方式

    基于XML配置 Beans.xml <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns= ...

  5. ssm整合(基于xml配置方式)

    本文是基于xml配置的方式来整合SpringMVC.Spring和Mybatis(基于注解的方式会再写一篇文章),步骤如下: (1)首先自然是依赖包的配置文件 pom.xml <project ...

  6. 02_Spring Bean的装配模式_基于XML配置方式

    一.三种实例化Bean的方式 1.使用类构造器实例化(默认无参数) <bean id="bean1" class="com.demo1.Bean1"> ...

  7. Spring IOC容器装配Bean_基于XML配置方式

    开发所需jar包 实例化Bean的四种方式 1.无参数构造器 (最常用) <?xml version="1.0" encoding="UTF-8"?> ...

  8. Spring 基于xml配置方式的事务

    参考前面的声明式事务的例子:http://www.cnblogs.com/caoyc/p/5632198.html 我们做了相应的修改.在dao中和service中的各个类中,去掉所有注解标签.然后为 ...

  9. Spring 基于xml配置方式的事务(14)

    参考前面的声明式事务的例子:http://www.cnblogs.com/caoyc/p/5632198.html 我们做了相应的修改.在dao中和service中的各个类中,去掉所有注解标签.然后为 ...

随机推荐

  1. Android开发:通过 webview 将网页打包成安卓应用

    商业转载请联系作者获得授权,非商业转载请注明出处. For commercial use, please contact the author for authorization. For non-c ...

  2. 更新Linux服务器时间

    1.修改系统时区(不修改的话,你同步时间会发现总是不对) ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime --这里我修改为了上海 2.安 ...

  3. 安装SQL Server 2008R2 报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本”解决方法

    安装SQL Server 2008 R2报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本,请在安装 SQL Server 2008 前将 VS2008 升级 ...

  4. linux环境下的时间编程

    Linux下提供了丰富的api以供开发者们处理和时间相关的问题.然而这些接口看似各自为政实则有有着千丝万缕的联系,在学习和时间中引发了各种各样的混乱.因此时间处理成为了许多Linux开发者的梦魇,遇到 ...

  5. 【原创】(六)Linux进程调度-实时调度器

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  6. Linux篇001——打开vi默认显示行号

    $ vi ~/.vimrc 新增一行命令 :set number 保存退出,source ~/.vimrc

  7. Django路由层与视图层、pycharm虚拟环境

    一. Django路由层 路由层即对应项目文件下的urls.py文件.实际上每个APP中也可以有自己的urls.py路由层.templates文件夹及static文件夹.Django支持这么做,也为实 ...

  8. laravel的中间件创建思路

    网上有很多解析laravel中间件的实现原理,但是不知道有没有读者在读的时候不明白,作者是怎么想到要用array_reduce函数的? 本文从自己的角度出发,模拟了如果我是作者,我是怎么实现这个中间件 ...

  9. Docker笔记(十三):容器日志采集实践

    日志是服务运行过程中的一个关键环节,借助日志,我们可以排查定位问题,也可以借助集中化的日志管理平台(如ELK)来做一些必要的数据统计分析.在Docker环境中,日志的采集比传统环境更为复杂,因此了解D ...

  10. python分布式接口,参数化实战二

    1,先看一下接口测试用例 2,文件1:写get和post模板 import requestsclass PostGetModels: def isMethod(self,url,data,method ...