前情回顾

上篇《Spring读书笔记——bean加载》我们从代码角度介绍了有哪些类负责解析XML文件,又是如何一步步从XML格式脱变成我们熟悉的bean的,直到DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法。我们抽丝剥茧,终于快看到Spring是如何解析bean的代码了。

在此之前,我们回一下上篇看到过的主要类和方法

XmlBeanFactory(XmlFactory) ->

XmlBeanDefinitionReader(loadBeanDefinitions)->

XmlBeanDefinitionReader(doLoadBeanDefinitions)->

XmlBeanDefintionReader(registerBeanDefinitions)

DefaultBeanDefinitionDocumentReader(registerBeanDefinitions)->

DefaultBeanDefinitonDocumentReader(doRegisterBeanDefinitions)

解析的类型

Spring的标签分为默认标签和自定义标签两种。

默认标签主要包括:import、alias、bean和beans

自定义标签:顾名思义就是自定义的标签,比如

DefaultBeanDefinitionDocumentReader

从XmlBeanDefinitionReader的registerBeanDefinitions开始,我们第一步要做的就是创建一个Xml文档的解析类

BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

createBeanDefinitionDocumentReader方法创建了类DefaultBeanDefinitionDocumentReader。

从DefaultBeanDefinitionDocumentReader的registerBeanDefinitions到doRegisterBeanDefinitions,下面我们着重从doRegisterBeanDefinitions的parseBeanDefintions方法开始往下看。

代理类BeanDefinitionParserDelegate

负责解析bean的并不是DefaultBeanDefinitionDocumentReader,而是委托给了代理类BeanDefinitionParserDelegate,后面我们会看到BeanDefinitionParserDelegate这个功臣是如何施展拳脚解析bean的。

parseBeanDefinitions(root, this.delegate);

进入parseBeanDefinitions我们看到其具体实现如下

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
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;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
  • 判断是否是默认命名空间的,如果是则遍历root下所有的子标签,如果不是,则进行自定义解析
  • 对于是默认命名空间下的标签,在遍历得到每一个子标签后开始调用parseDefaultElement方法开始我们之前提到的默认标签的解析。

默认标签解析

前面已经提到过默认标签有四种,我们看看代码也是一目了然,下面是解析默认标签的parseDefaultElement的方法实现

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
  • if...else语句分别判定了import、alias、bean和beans的情况,并且不同情况作了不同的方法实现
  • 首先要看的就是我们再熟悉不过的bean标签的解析processBeanDefinition方法

bean标签的解析

processBeanDefinition的方法实现如下

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

方法并不复杂,大致看下主要干了下面几件事

  • 前面介绍的BeanDefinitionParserDelegate开始排上用场,用于对bean元素解析,并得到BeanDefinitionHolder类型的bdHolder对象
  • 对于解析得到结果不为空的情况,首先还要对于需要修饰的情况(默认标签包含自定义标签)进行处理
  • 然后借助BeanDefinitionReaderUtils的registerBeanDefinition对bdHolder进行注册
  • 最后发送响应事件,通知响应的监听器(Spring3x版本这块还没有具体实现,只是留了个壳)

delegate.parseBeanDefinitionElement

我们主要从processBeanDefintion的第一行代码开始,来到BeanDefinitionParserDelegate.parseBeanDefinitionElement

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
} String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
} if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
} AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
} return null;
}
  • 有很多我们之前熟知的细节从这里的代码就可以找到根据,比如“bean如果不声明name属性,spring会默认创建一个name”,显然通过上面一点代码我们就看到Spring确实是这么实现的,而且我们看到如果没有声明name,默认的name值就是id的值
  • 紧接着,我们看下

    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);这行代码,进入parseBeanDefinitionElement我们会看到这个方法做了很多的解析的工作,比如解析parent属性、解析元数据、解析构造函数、解析property子元素等等

parseBeanDefinitionElement方法

public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
} try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
AbstractBeanDefinition bd = createBeanDefinition(className, parent); parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele)); return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
} return null;
}

我们挑其中一个解析属性函数parseConstructorArgElements看下具体实现。

parseConstructorArgElements用于解析ConstructArg标签。

我们常见的construct-arg大概是这样

<bean id="user" class="com.jackie.User">
​<construct-arg index="0">
​ ​<value>jackie</value>
​</construct-arg>
​<construct-arg index="1">
<value>male</value>
</construct-arg>
</bean>

construct-arg标签的解析过程如下所示

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(indexAttr)) {
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
}
else {
try {
this.parseState.push(new ConstructorArgumentEntry(index));
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
error("Ambiguous constructor-arg entries for index " + index, ele);
}
else {
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
}
finally {
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
else {
try {
this.parseState.push(new ConstructorArgumentEntry());
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
}
finally {
this.parseState.pop();
}
}
}
  • 如果constrcut-arg没有指定index函数,默认按照声明属性的顺序来解析,如果声明则按照index的值解析
  • 使用ConstrcutorArgumentValues的ValueHolder来封装解析得到的type、name等属性值
  • 最后将上面的valueHolder对象按照index添加到BeanDefinition的indexedArgument属性上
  • 注意,对于没有index声明的,最终解析后是存放到genericArgumentValues属性上

bean标签的注册

上面层层剥开,一直到解析到具体的属性,我们现在再一层层的往回走。前面所做的工作,无非是将Xml的元素解析出来并与Spring中的BeanDefinition,也就是换了一种形式在Spring中存在。

我们最终得到的是BeanDefinitionHolder

public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, String[] aliases) {
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
Assert.notNull(beanName, "Bean name must not be null");
this.beanDefinition = beanDefinition;
this.beanName = beanName;
this.aliases = aliases;
}

解析转换工作完成了,现在需要开始bean的注册了

我们回到DefaultBeanDefinitionDocumentReader的processBeanDefinition,看看注册是如何实现的。

从DefaultBeanDefinitionDocumentReader.processBeanDefinition->BeanDefinitionReaderUtils.registerBeanDefinition->DefaultLisableBeanFactory.registerBeanFactory。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
} synchronized (this.beanDefinitionMap) {
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
else {
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
} resetBeanDefinition(beanName);
}
  • 如果beanName已经注册又没有声明可以覆盖,则会抛出异常
  • 符合条件的bean最终会被加入一个map结合
  • map集合的key是beanName,value就是前面解析得到的BeanDefinition实例对象
  • 清楚解析之前留下对应的beanName的缓存

至此,我们看到了Spring是如何解析标签(我们还有自定义标签没说),如何解析bean标签(我们还有import、alias和beans没说),如何解析标签属性(我们还有太多的属性没有介绍,但是都是类似的过程),最终完成了bean的注册。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Spring读书笔记——bean解析的更多相关文章

  1. Spring读书笔记——bean创建(上)

    通过<Spring读书笔记--bean加载>和<Spring读书笔记--bean解析>,我们明白了两件事. Spring如何加载消化一个xml配置文件 Spring如何将xml ...

  2. Spring读书笔记——bean创建(下)

    有关Spring加载bean系列,今天这是最后一篇了,主要接上篇对于从Spring容器中获取Bean的一些细节实现的补充. <Spring读书笔记--bean加载>--Spring如何加载 ...

  3. Spring读书笔记——bean加载

    我们的日常开发几乎离不开Spring,他为我们的开发带来了很大的便捷,那么Spring框架是如何做到方便他人的呢.今天就来说说bean如何被加载加载. 我们在xml文件中写过太多类似这样的bean声明 ...

  4. (转) Spring读书笔记-----Spring的Bean之配置依赖

    前一篇博客介绍了Spring中的Bean的基本概念和作用域(Spring读书笔记-----Spring的Bean之Bean的基本概念),现在介绍Spring Bean的基本配置. 从开始我们知道Jav ...

  5. Spring读书笔记-----Spring的Bean之设置Bean值

    [java] view plaincopyprint? Java实例的属性值可以有很多种数据类型.基本类型值.字符串类型.java实例甚至其他的Bean实例.java集合.数组等.所以Spring允许 ...

  6. Spring读书笔记-----Spring的Bean之Bean的基本概念

    从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架 ...

  7. (转)Spring读书笔记-----Spring的Bean之Bean的基本概念

    从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架 ...

  8. (转)Spring读书笔记-----Spring核心机制:依赖注入

    Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因此,我们说这些对象间存在依赖关系.加入A组件调用了B组件的方法,我们就 ...

  9. Spring读书笔记-----Spring核心机制:依赖注入

    spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入.今天就和大家一起来学习一下 依赖注入的基本概念 依赖注入(Dependecy Injection),也称为IoC(I ...

随机推荐

  1. HIVE和HBASE区别11

    对于刚接触大数据的用户来说,要想区分Hive与HBase是有一定难度的.本文将尝试从其各自的定义.特点.限制.应用场景等角度来进行分析,以作抛砖引玉之用.  Hive是什么? Apache Hive是 ...

  2. GUI(自定义背景图片)

    如果组件中没有setIcon(...);这个方法,这是有需要给组件设置背景图片,这时就可以自定义绘制背景图片 /** * */ package com.niit.javagui; import jav ...

  3. 结对编程1---基于Flask的四则运算题目生成器

    项目代码地址 / WEB应用地址 / 合作伙伴iFurySt博文链接 需求分析 本次程序是基于原有的控制台四则运算器的基础上,改成WEB的形式,同时还增加了一些新的功能.同时因为交互方式的改变,代码也 ...

  4. 201521123010 《Java程序设计》第7周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 参考资料: XMind 2. 书面作业 ①ArrayList代码分析 1.1 解释ArrayList的contains源代码 ...

  5. 201521123113《Java程序设计》第13周学习总结

    1. 本周学习总结 2. 书面作业 Q1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分析返回结果有何不同?为什么会有这样的不同? 返回的结果 ...

  6. php中的多条件查询

    首先是查询所有,步骤不详述,连接数据库,查询表中的所有信息,foreach循环以表格的形式打印出来 然后就是form表单中提交查询的数据,这里以post方式提交到本页面,所以要判断post中是否有值, ...

  7. 如何加固Linux系统

    如何加固Linux系统 一. 账户安全 1.1 锁定系统中多余的自建帐号 检查方法: 执行命令 #cat /etc/passwd #cat /etc/shadow 查看账户.口令文件,与系统管理员确认 ...

  8. HTML基础入门

    1.什么是HTML 2.HTML文件结构 3.HTML文档 4.HTML标签 1.什么是HTML 首先,HTML是一种语言,是用来描述网页的语言 HTML 指的是超文本标记语言 (Hyper Text ...

  9. OC Block网上转载

    1.block是一个特殊的OC对象, 它建立在栈上, 而不是堆上, 这么做一个是为性能考虑,还有就是方便访问局部变量. 2.默认情况下block使用到的局部变量都会被复制,而不是保留.所以它无法改变局 ...

  10. js如何获取客户端IP

    1.在HTML页面里面引入<script src="http://pv.sohu.com/cityjson?ie=utf-8"></script> 2.获取 ...