第3章自定义标签解析
在第1章中对Spring的简单使用进行了说明,简单介绍了bean标签的使用。bean标签属于Spring的原生标签,在Spring中除了原生标签以外还能够支持自定义标签,本章将介绍SpringXML配置文件中的自定义标签如何进行自定义、如何使用自定义标签,并对SpringXML的自定义标签相关的内容进行源码分析。
3.1创建自定义标签环境搭建
在开始自定义标签分析之前,需要先编写自定义标签解析相关的测试用例,编写自定义标签需要执行下面四个步骤。
(1) 编写XSD文件或者DTD文件。
(2) 编写NamespaceHandler实现类。
(3) 编写BeanDefinitionParser实现类。
(4) 编写注册方式,向Spring中注册。
接下来对上述四个步骤做详细说明。
3.1.1编写 XSD 文件
首先编写一个Java对象用来存储自定义标签解析后的数据,编写UserXsdJava对象,代码信息如下。

//省略getter&setter

public class UserXsd {

private String name; 

private String idCard; 

}

完成XSD文件解析结果的存储对象后进一步编写XSD文件,该XSD文件名为user.xsd,文件内容如下。

<?xml version="1.0" encoding="UTF-8"?>

<schema xmlns="http: //www.w3.org/2001/XMLSchema"

targetNamespace="http: //www.huifer.com/schema/user"

elementFormDefault="qualified">



<element name="user_xsd">

<complexType>

<attribute name="id" type="string"/>

<attribute name="name" type="string"/>

<attribute name="idCard" type="string"/>

</complexType>

</element>

</schema>

3.1.2编写 NamespaceHandler 实现类
完成XSD文件编写后进一步编写NamespaceHandler接口的实现类,Spring提供了NamespaceHandlerSupport对象让开发者更加简单地使用,开发者只需要重写init方法即可向Spring注册标签和标签的解析对象,编写UserXsdNamespaceHandler类,详细代码如下。

public class UserXsdNamespaceHandler extends NamespaceHandlerSupport {



@Override

public void init() {

registerBeanDefinitionParser("user_xsd",new UserXsdParser()); 

}



}

3.1.3编写 BeanDefinitionParser 实现类
在编写NamespaceHandler实现类的时候引入了一个新的Java对象UserXsdParser,该对象是BeanDefinitionParser接口的实现类,在Spring中可以通过继承AbstractSingleBeanDefinitionParser类重写getBeanClass和doParse方法即可完成BeanDefinitionParser的实现,下面是UserXsdParser的代码内容。

public class UserXsdParser extends AbstractSingleBeanDefinitionParser {

@Override

protected Class<?> getBeanClass(Element element) {

return UserXsd.class; 

}



@Override

protected void doParse(Element element,BeanDefinitionBuilder builder) {

String name = element.getAttribute("name"); 

String idCard = element.getAttribute("idCard"); 

builder.addPropertyValue("name",name); 

builder.addPropertyValue("idCard",idCard); 

}



}

在这段代码中通过提取Element对象的name和idCard属性将其设置到BeanDefinitionBuilder对象的属性表中。
3.1.4编写注册方式
下面编写注册方式。注册自定义标签解析能力需要编写两个文件,一个是spring.handlers文件,另一个是spring.schemas文件。在这个测试用例中需要向spring.handlers文件中填写下面这段内容。

http\://www.huifer.com/schema/user=com.source.hot.ioc.book.namespace.handler.

UserXsdNamespaceHandler

对于spring.handlers文件可以分成两部分来进行理解,第一部分是等号前面的内容,等号前的内容是指命名空间和XSD文件中schema中的targetNamespace属性之间的关系; 第二部分是等号后面的内容,它是指接口NamespaceHandler实现类的完整类路径。
完成spring.handlers编写后进一步编写spring.schemas文件,向spring.schemas文件中添加下面这段代码。

http\://www.huifer.com/schema/user.xsd=META-INF/user.xsd

对于spring.schemas文件可以分成两部分来进行理解,第一部分是等号前面的内容,它是指schemaLocation的一个链接地址; 第二部分是等号后面的内容,它是指schemaLocation对应的XSD文件描述路径。
3.1.5测试用例的编写
完成了各项基本准备工作后进一步编写一个自定义标签处理的Java程序,首先需要编写SpringXML配置文件,文件名为customxml.xml,向customxml.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: myname="http://www.huifer.com/schema/user"

xsi: schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.huifer.com/schema/user http://www.huifer.com/schema/user.xsd

">



<myname: user_xsd id="testUserBean" name="huifer" idCard="123"/>



</beans>

完成SpringXML配置文件的编写后再编写一个测试类,测试类名称为CustomXmlTest,CustomXmlTest中代码如下。

class CustomXmlTest {



@Test

void testXmlCustom() {

ClassPathXmlApplicationContext context =

 new ClassPathXmlApplicationContext("META-INF/custom-xml.xml"); 

UserXsd testUserBean = context.getBean("testUserBean",UserXsd.class); 

assert testUserBean.getName().equals("huifer"); 

assert testUserBean.getIdCard().equals("123"); 

context.close(); 

}

}

完成测试类及测试方法的编写后自定义标签测试环境即搭建完成。
3.2自定义标签解析
下面将进入自定义标签解析相关源代码分析,首先需要找到自定义标签解析的源码入口,该入口的方法签名为org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element,org.springframework.beans.factory.config.BeanDefinition),详细代码如下。

@Nullable

public BeanDefinition parseCustomElement(Element ele,@Nullable BeanDefinition containingBd) {

//获取命名空间的URL

String namespaceUri = getNamespaceURI(ele); 

if (namespaceUri == null) {

return null; 

}

//命名空间处理器

NamespaceHandler handler 

= this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 

if (handler == null) {

error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]",ele); 

return null; 

}

return handler.parse(ele,new ParserContext(this.readerContext,this,containingBd)); 

}

3.2.1NamesapceHandler和 BeanDefinitionParser 之间的关系



图3.1NamespaceHandler关系图

parseCustomElement方法中体现了namespaceUri的一些关系,在测试用例中namespaceUri和spring.handlers中的文件存在关系,命名空间对应命名空间处理器,通过这个关系可以确认NamespaceHandler是UserXsdNamespaceHandler对象,在UserXsdNamespaceHandler类中有init方法来注册标签和标签的解析能力提供类的关系。具体关系如图3.1所示。
3.2.2获取命名空间地址
接下来将介绍命名空间地址的获取,命名空间地址及namespaceUri属性,获取该属性的方法是由org.w3c.dom提供,具体细节不做展开,获取命名空间地址后的数据是http://www.huifer.com/schema/user。
3.2.3NamespaceHandler对象获取
通过前文获得了namespaceUri数据信息后会通过该信息寻找到对应的NamespaceHandler对象,具体处理方法如下。

NamespaceHandler handler 

= this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 

在这段方法中负责命名空间解析的对象是NamespaceHandlerResolver接口,NamespaceHandlerResolver接口定义如下。

@FunctionalInterface

public interface NamespaceHandlerResolver {



/**

* 解析命名空间的 URL 获得命名空间处理器

*/

@Nullable

NamespaceHandler resolve(String namespaceUri); 



}

在Spring中它只有一个实现类DefaultNamespaceHandlerResolver,在DefaultNamespaceHandlerResolver中有一个成员变量DEFAULT_HANDLER_MAPPINGS_LOCATION,成员变量详细信息如下。

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = 

"META-INF/spring.handlers"; 

在DEFAULT_HANDLER_MAPPINGS_LOCATION成员变量中定义了默认的命名空间处理器存储路径,spring.handlers中存储的内容是(key,value)结构,key是命名空间,value是命名空间处理器的类全路径,接下来查看resolve方法,详细代码如下。

//删除异常处理和日志

public NamespaceHandler resolve(String namespaceUri) {

//获取 Namespace Handler 映射表

Map < String,Object > handlerMappings = getHandlerMappings(); 

//从映射表中获取URI对应的 Handler

//字符串(名称)

//实例

Object handlerOrClassName = handlerMappings.get(namespaceUri); 

if(handlerOrClassName == null) {

return null; 

} else if(handlerOrClassName instanceof NamespaceHandler) {

return(NamespaceHandler) handlerOrClassName; 

}

//其他情况都做字符串处理

else {

String className = (String) handlerOrClassName; 

Class <? > handlerClass = ClassUtils.forName(className,this.classLoader); 

//通过反射构造 namespaceHandler 实例

NamespaceHandler namespaceHandler = 

(NamespaceHandler) BeanUtils.instantiateClass(handlerClass); 

//初始化

namespaceHandler.init(); 

//重写缓存

handlerMappings.put(namespaceUri,namespaceHandler); 

return namespaceHandler; 

}

}

在resolve方法中整体处理流程如下。
(1) 获取命名空间映射关系,即命名空间地址(namespaceUri)对应命名空间解析对象类全路径,这个关系是一个 map集合。
(2) 从map集合中获取命名空间地址(namespaceUri)对应的数据。
(3) 根据提取数据的不同情况进行处理。
① 通过命名空间地址获取的对象是NamespaceHandler类型,直接返回使用。
② 通过命名空间地址获取的对象不是NamespaceHandler类型。当类型不是NamespaceHandler对象时,它的类型只可能是字符串类型,字符串是没有办法直接调用NamespaceHandler所提供的方法的,因此需要将字符串转换成NamespaceHandler对象。
3.2.4getHandlerMappings获取命名空间的映射关系
在前文讲到字符串转换为NamespaceHandler的内容在本节会对其做补充,负责这部分处理的方法是getHandlerMappings,详细代码如下。

//删除异常处理和日志

private Map < String,Object > getHandlerMappings() {

//设置容器

Map < String,Object > handlerMappings = this.handlerMappings; 

if(handlerMappings == null) {

synchronized(this) {

handlerMappings = this.handlerMappings; 

if(handlerMappings == null) {

//读取资源文件地址

Properties mappings = 

PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation,this.classLoader); 

handlerMappings = new ConcurrentHashMap < > (mappings.size()); 

//数据合并,将 mappings 数据复制给 handlerMappings

CollectionUtils.mergePropertiesIntoMap(mappings,handlerMappings); 

this.handlerMappings = handlerMappings; 

}

}

}

return handlerMappings; 

}

在这段方法中首先需要关注的是下面这段代码。

Properties mappings =

PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation,this.classLoader); 

这段代码是用来提取METAINF/spring.handlers文件中的数据内容,Spring将METAINF/spring.handlers文件当作拓展名为properties类型的文件进行处理,处理之后得到的是Map对象,在完成METAINF/spring.handlers文件的读取后进行了合并操作,分别将历史的handlerMappings和本次读取得到的handlerMappings进行合并。图3.2为本次读取得到的handlerMappings数据。


图3.2handlerMappings数据信息


通过图3.2可以看到一条这样的信息: http://www.huifer.com/schema/user > com.source.hot.ioc.book.namespace.handler.UserXsdNamespaceHandler,这段信息的来源是前文在测试用例中文件spring.handlers的内容,除此之外,其他的数据是Spring项目中存放的,文件依旧还是在METAINF/spring.handlers中。
springbeans工程下的spring.handlers数据信息如下。

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.

SimpleConstructorNamespaceHandler

http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.

SimplePropertyNamespaceHandler

http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.

UtilNamespaceHandler

springaop工程下的spring.handlers数据信息如下。

http\://www.springframework.org/schema/aop=org.springframework.aop.config.

AopNamespaceHandler

springcontext工程下的spring.handlers数据信息如下。

http\://www.springframework.org/schema/context=org.springframework.context.config.

ContextNamespaceHandler

http\://www.springframework.org/schema/jee=org.springframework.ejb.config.

JeeNamespaceHandler

http\://www.springframework.org/schema/lang=org.springframework.scripting.config.

LangNamespaceHandler

http\://www.springframework.org/schema/task=org.springframework.scheduling.config.

TaskNamespaceHandler

http\://www.springframework.org/schema/cache=org.springframework.cache.config.

CacheNamespaceHandler

上述这些文件是Spring容器中提供的NamespaceHandler数据,除此之外,还有一些其他的spring.handlers文件本文不做赘述。
3.2.5NamespaceHandler的获取
在前面的操作中已经准备好handlerMappings数据对象,下面需要将数据对象进行初始化(实例化,实例化的前提是类型不是NamespaceHandler)。有关NamespaceHandler的获取代码如下。

Object handlerOrClassName = handlerMappings.get(namespaceUri); 

if(handlerOrClassName == null) {

return null; 

} else if(handlerOrClassName instanceof NamespaceHandler) {

return(NamespaceHandler) handlerOrClassName; 

}

//其他情况都做字符串处理

else {

String className = (String) handlerOrClassName; 

Class <? > handlerClass = ClassUtils.forName(className,this.classLoader); 

//通过反射构造 namespaceHandler 实例

NamespaceHandler namespaceHandler = 

(NamespaceHandler) BeanUtils.instantiateClass(handlerClass); 

//初始化

namespaceHandler.init(); 

//重写缓存

handlerMappings.put(namespaceUri,namespaceHandler); 

return namespaceHandler; 

}

上述代码中有以下三种情况需要处理。
(1) 容器中没有当前namespaceUri的值。
(2) 容器中有当前namespaceUri的值,并且类型是NamespaceHandler。
(3) 容器中有当前namespaceUri的值,但类型不是NamespaceHandler。
对于上述三种情况需要重点分析的是第三种。在第三种情况中,value的数据类型是String,Spring需要将String类型转换为最终的Java对象,通过类全路径转换成Java对象需要执行以下三个步骤。
(1) 通过Class.forName 得到Class对象。
(2) 通过Class对象提取构造函数。
(3) 通过构造函数创建实例。
上述三个操作步骤可以分别对应下面这些代码,第一步对应“Class<?> handlerClass = ClassUtils.forName(className,this.classLoader);”,第二步和第三步对应Spring项目中的一个工具类BeanUtils。通过BeanUtils.instantiateClass方法即可得到Java对象。
3.2.6NamespaceHandler的 init 方法
接下来将介绍NamespaceHandler的init方法,在测试用例中,UserXsdNamespaceHandler对象继承NamespaceHandlerSupport类,接下来需要分析的重点内容都在NamespaceHandlerSupport类中。首先需要指出一点,在Spring中的spring.handlers文件中的实现类都有继承NamespaceHandlerSupport类,可见它的重要性。在测试用例中使用了registerBeanDefinitionParser方法进行了标签和解析类的注册,下面对registerBeanDefinitionParser方法进行分析,先看registerBeanDefinitionParser代码。

protected final void registerBeanDefinitionParser(String elementName,BeanDefinitionParser parser) {

this.parsers.put(elementName,parser); 

}

在这段代码中需要了解以下两个参数的含义。
(1) elementName: XML标签的名称。
(2) parser: 提供标签解析能力的实际对象,是BeanDefinitionParser接口的实现类。
下面介绍parsers的数据结构,在Spring中对parsers的定义如下。

private final Map<String,BeanDefinitionParser> parsers = new HashMap<>(); 

key表示XML标签的名称,value表示BeanDefinitionParser接口的实现类。parsers的数据存储情况如图3.3所示。


图3.3parsers数据信息


3.2.7NamespaceHandler缓存的刷新
在NamespaceHandler的生命周期方法init执行完成后会刷新namespaceUri对应的value,此时会产生handlerMappings中value的多种情况。
(1) value不存在。
(2) value存在,且是NamespaceHandler实例。
(3) value存在,且是字符串。
刷新handlerMappings变量的操作代码如下。

handlerMappings.put(namespaceUri,namespaceHandler)

这段代码的执行就是将namespaceUri和namespaceHandler的关系重新绑定,此时放入的value就会变成NamespaceHandler实例,后续在需要使用时就会进入下面这段代码。

else if (handlerOrClassName instanceof NamespaceHandler) {

return (NamespaceHandler) handlerOrClassName; 

}

在这里 Spring 通过了一个 Map 对象的刷新操作来提高了性能,避免了每次从字符串出发进行反射获取实例,获取实例之后再做其他操作。图3.4为经过刷新操作后的handlerMappings数据情况。


图3.4刷新后的handlerMappings


3.2.8解析标签BeanDefinitionParser对象准备
接下来将进入自定义标签解析的重要环节——BeanDefinitionParser实现类的准备阶段。首先阅读parse方法: 

NamespaceHandler handler 

= this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 

return handler.parse(ele,new ParserContext(this.readerContext,this,containingBd)); 

在这段代码中得到了NamespaceHandler对象,这个对象的实际类型是UserXsdNamespaceHandler,确认实际类型后对于parse方法入口的查询有了方向,parse方法位于UserXsdNamespaceHandler的父类NamespaceHandlerSupport中,具体代码如下。

public BeanDefinition parse(Element element,ParserContext parserContext) {

//搜索 element 对应的  BeanDefinitionParser

BeanDefinitionParser parser = findParserForElement(element,parserContext); 

//解析

return (parser != null ? parser.parse(element,parserContext):  null); 

}

从这段操作代码中可以发现一个重点类BeanDefinitionParser,在测试用例中编写的内容中有一个与之存在关系,这个类是UserXsdParser。在测试用例中和UserXsdParser对象产生关系的内容是一个注册方法registerBeanDefinitionParser("user_xsd",new UserXsdParser()),这个方法表示user_xsd标签需要通过UserXsdParser对象进行解析。在parse方法中出现的findParserForElement方法目的就是通过标签名称找到对应的标签处理对象。findParserForElement方法代码如下。

private BeanDefinitionParser findParserForElement(Element element,ParserContext parserContext) {

//获取 element 的名称

String localName = parserContext.getDelegate().getLocalName(element); 

//从容器中获取

BeanDefinitionParser parser = this.parsers.get(localName); 

if (parser == null) {

parserContext.getReaderContext().fatal(

"Cannot locate BeanDefinitionParser for element [" + localName + "]",element); 

}

return parser; 

}

在这段方法中可以看到以下两个处理步骤。
(1) 提取Element的名称数据。
(2) 从parsers中获取解析BeanDefinitionParser对象。
此时得到的BeanDefinitionParser是UserXsdParser对象。
3.2.9解析标签parse方法调用
在Spring中parse方法提供者是AbstractBeanDefinitionParser对象,详细代码如下。

//删除异常处理和日志

public final BeanDefinition parse(Element element,ParserContext parserContext) {

//解析 Element 得到 BeanDefinition 对象

AbstractBeanDefinition definition = parseInternal(element,parserContext); 

if(definition != null && !parserContext.isNested()) {

//解析标签的 id

String id = resolveId(element,definition,parserContext); 

if(!StringUtils.hasText(id)) {

parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag",element); 

}

String[] aliases = null; 

if(shouldParseNameAsAliases()) {

//标签的 name 属性

String name = element.getAttribute(NAME_ATTRIBUTE); 

if(StringUtils.hasLength(name)) {

//别名处理,根据逗号进行字符串切割

aliases = 

StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); 

}

}

//创建 BeanDefinitionHolder

BeanDefinitionHolder holder = new 

BeanDefinitionHolder(definition,id,aliases); 

//注册 BeanDefinition

registerBeanDefinition(holder,parserContext.getRegistry()); 

//是否需要触发事件

if(shouldFireEvents()) {

//组件注册事件

BeanComponentDefinition componentDefinition = 

new BeanComponentDefinition(holder); 

postProcessComponentDefinition(componentDefinition); 

parserContext.registerComponent(componentDefinition); 

}

}

return definition; 

}



图3.5UserXsdParser类图

在这段代码中比较难以查看到UserXsdParser对象的踪迹,UserXsdParser类图信息如图3.5所示。
接下来看第一段代码。

AbstractBeanDefinition definition = parseInternal

(element,parserContext); 

parseInternal方法在AbstractBeanDefinitionParser是一个抽象方法,真正的实现在 AbstractSingleBeanDefinitionParser中,下面是AbstractSingleBeanDefinitionParser#parseInternal方法代码。

@Override

protected final AbstractBeanDefinition parseInternal(Element element,ParserContext parserContext) {

//BeanDefinition 构造器

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); 

//获取 parent 属性

String parentName = getParentName(element); 

if (parentName != null) {

builder.getRawBeanDefinition().setParentName(parentName); 

}



//获取 class 属性

Class<?> beanClass = getBeanClass(element); 

if (beanClass != null) {

builder.getRawBeanDefinition().setBeanClass(beanClass); 

}

else {

String beanClassName = getBeanClassName(element); 

if (beanClassName != null) {

builder.getRawBeanDefinition().setBeanClassName(beanClassName); 

}

}

//设置源

builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); 



//获取已存在的 BeanDefinition,该对象仅用来设置 scope 属性

BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); 

if (containingBd != null) {

builder.setScope(containingBd.getScope()); 

}

if (parserContext.isDefaultLazyInit()) {

builder.setLazyInit(true); 

}

//真正的调用

doParse(element,parserContext,builder); 

//BeanDefinition 构造器中获取 BeanDefinition

return builder.getBeanDefinition(); 

}

在UserXsdParser类中重写了getBeanClass和doParse方法,测试用例中所编写的doParse方法的调用需要通过一层外部调用才可以抵达测试用例中UserXsdParser类的doParse方法,具体调用过程的代码如下。

//AbstractSingleBeanDefinitionParser#parseInternal 中调用

protected void doParse(Element element,ParserContext parserContext,

BeanDefinitionBuilder builder) {

doParse(element,builder); 

}

//需要重写的方法

protected void doParse(Element element,BeanDefinitionBuilder builder) {

}

AbstractSingleBeanDefinitionParser#parseInternal方法处理的细节如下。
(1) 准备基本数据,基本数据包含parentName、beanClass、source和scope。
(2) 执行自定义的doParse方法。
(3) 通过BeanDefinitionBuilder来获取 BeanDefinition。
在parse方法中首先需要进行id的处理,代码如下。

String id = resolveId(element,definition,parserContext); 

在这段代码中对于id属性的获取其本质是提取XML标签中的id属性。完成id数据获取后需要执行的事项是针对别名的处理,相关代码如下。

String[] aliases = null; 

if (shouldParseNameAsAliases()) {

//标签的 name 属性

String name = element.getAttribute(NAME_ATTRIBUTE); 

if (StringUtils.hasLength(name)) {

//别名处理,根据逗号进行字符串切割

aliases = 

StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); 

}

}

在别名处理阶段会判断是否需要处理别名,默认是都需要处理的。别名处理方式是将alias标签中的name属性根据分隔符(逗号)切分,关于切分其本质为name.split(“,”),具体的处理方法是StringUtils.commaDelimitedListToStringArray。在数据信息准备完成之后需要进行BeanDefinition对象的注册和事件发布,相关代码如下。

BeanDefinitionHolder holder = 

new BeanDefinitionHolder(definition,id,aliases); 

//注册 BeanDefinition

registerBeanDefinition(holder,parserContext.getRegistry()); 

//是否需要出发事件

if (shouldFireEvents()) {

//组件注册事件

BeanComponentDefinition componentDefinition = 

new BeanComponentDefinition(holder); 

postProcessComponentDefinition(componentDefinition); 

parserContext.registerComponent(componentDefinition); 

}

在这部分代码处理中,关于事件发布相关内容是一个预留方法,暂时是一个空处理,对于BeanDefinition对象的注册会在后续章节中进行详细分析,在这仅需要了解BeanDefinition对象的存储容器: 

private final Map<String,BeanDefinition> beanDefinitionMap = 

new ConcurrentHashMap<>(256); 

当BeanDefinition 对象被放置到容器后这段方法的处理就完成了。
小结
通过本章的阐述,相信读者对于自定义标签的处理流程有了更加详细的认知。在本章了解了NamespaceHandler、NamespaceHandlerSupport、AbstractSingleBeanDefinitionParser、AbstractBeanDefinitionParser和BeanDefinitionParser之间的关系,这些内容在 Spring 中是一个十分重要的技术点,Spring 后续的一些内容都强依赖于这一门技术(自定义标签解析),常见的有 SpringMVC、Spring事务、SpringAOP 等,它们都是基于此作为一个拓展实现了更强大的功能。