Spring读书笔记——bean解析

时间:2022-04-05 07:44:32

前情回顾

上篇《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解析

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. &lpar;转&rpar;Spring读书笔记-----Spring核心机制:依赖注入

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

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

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

随机推荐

  1. hadoop命令报错:权限问题

    root用户执行hadoop命令报错: [root@vmocdp125 conf]# hadoop fs -ls /user/ [INFO] 17:50:42 main [RetryInvocatio ...

  2. 【转】JNI学习积累之一 ---- 常用函数大全

    原文网址:http://blog.csdn.net/qinjuning/article/details/7595104 本文原创,转载请注明出处:http://blog.csdn.net/qinjun ...

  3. 关于vs2005 &lowbar;&lowbar;RPC&lowbar;&lowbar;out &lowbar;&lowbar;RPC&lowbar;&lowbar;in 没有定义编译错误

    1. 下载WDK http://www.microsoft.com/en-us/download/details.aspx?id=11800 2. 安装WDK 3. vs2005 设置:工具--&gt ...

  4. iOS NSString 文本不同的颜色 标题&plus;文本字体大小 行间距/删除不需要的字符 /以及自适应高度

    #import <Foundation/Foundation.h> @interface TextsForRow : NSObject @property(nonatomic,copy)N ...

  5. 【推荐】Data Structure Visualizations

    University of San Francisco    David Galles 功能:可视化数据结构&算法实现过程 网站地址  https://www.cs.usfca.edu/~ga ...

  6. 06-OpenLDAP密码策略

    阅读视图 openldap密码策略 OpenLDAP服务端定制密码策略 客户端策划策略实例 定义用户第一次登录就修改密码 问题排查手册 重点推荐官方文档 备注:本文依然承接系列文. 1. openld ...

  7. python subprocess中ssh命令的特殊性

    今天尝试使用python 的 subprocess 模块 使用类似如下语句: p=subprocess.Popen(['ssh','root@localhost'],stdout=subprocess ...

  8. Failed to unregister the JMX name&colon; org&period;apache&period;commons&period;dbcp2&colon;name&equals;xxx&comma;type&equals;BasicDataSource

    把datesource的bean的class由 org.apache.commons.dbcp2.BasicDataSource 改成 org.apache.tomcat.dbcp.dbcp.Basi ...

  9. asp&period;net mvc controller调用js

    在controller中加入代码 ViewBag.js="<script>alert('弹出框');</script>"; 在视图中加入代码 @Html.R ...

  10. NGS的duplicate的问题

    NGS的duplicate的问题   duplicate的三个问题: 一.什么是duplicate? 二.duplicate来源? 三.既然PCR将1个reads复制得到成百上千copies,那为什么 ...