十三、解析bean标签子元素二

解析bean标签子元素二

自定义标签的使用

自定义标签的使用大致分为以下步骤:

  1. 定义一个自定义标签组件,就是一个普通的Java Bean
  2. 定义一个xsd文件,用来描述自定义标签的属性内容。
  3. 创建一个实现了 org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser 接口的Parser,用来解析xsd文件中的自定义标签内容。
  4. 创建一个继承了 org.springframework.beans.factory.xml.NamespaceHandlerSupport 抽象类的NameSpaceHandler,用于将自定义标签组件注册到 Spring 容器。
  5. 编写 spring.handlersspring.schemas 文件,放在resource/META-INF目录下。

文件结构图:

自定义标签目录结构图

自定义标签组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Hello {
private String id;
private Integer age;
private String name;
private String address;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public Hello() {
}

public void sayHello() {
System.out.println(this.name + "今年" + this.age + "岁," + "住在" + this.address);
}
}
定义xsd文件

该文件用来对自定义标签组件进行描述,还定义了xmlns="http://www.jievhaha.com/schema/hello" targetNamespace="http://www.jievhaha.com/schema/hello"

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.jievhaha.com/schema/hello" targetNamespace="http://www.jievhaha.com/schema/hello" elementFormDefault="qualified">
<xsd:element name="hello">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="age" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="address" type="xsd:string" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
定义Parser

该类继承了AbstractSingleBeanDefinitionParser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class HelloDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return Hello.class;
}

@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String id = element.getAttribute("id");
String age = element.getAttribute("age");
String name = element.getAttribute("name");
String address = element.getAttribute("address");
if(StringUtils.hasLength("id")){
builder.addPropertyValue("id", id);
}
if(StringUtils.hasLength(age)){
builder.addPropertyValue("age", age);
}
if(StringUtils.hasText(name)){
builder.addPropertyValue("name", name);
}
if(StringUtils.hasLength(address)){
builder.addPropertyValue("address", address);
}
}
}
定义NameSpaceHandler

该类继承了NamespaceHandlerSupport

1
2
3
4
5
6
public class HelloNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("hello",new HelloDefinitionParser());
}
}
定义spring.handlersspring.schemas文件
1
2
3
4
5
#spring.handlers
http\://www.jievhaha.com/schema/hello=com.jievhaha.HelloNameSpaceHandler

#spring.schemas
http\://www.jievhaha.com/schema/hello.xsd=hello.xsd
配置spring配置文件并测试

spring.xml

1
2
3
4
5
6
7
8
9
<?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:mytag="http://www.jievhaha.com/schema/hello"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.jievhaha.com/schema/hello http://www.jievhaha.com/schema/hello.xsd">

<mytag:hello id="hello" name="jievhaha" age="27" address="山西"/>
</beans>

MyApplication.java

1
2
3
4
5
6
7
8
public class MyApplication {
public static void main(String[] args) {

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("hello.xml");
Hello hello = (Hello) applicationContext.getBean("hello");
hello.sayHello();//jievhaha今年27岁,住在山西
}
}

自定义标签运行结果

解析自定义标签

parseCustomElement()方法

applicationContext.xml文件开始读取的入口方法:DefaultBeanDefinitionDocumentReader.parseBeanDefinitions()方法,该方法负责标签的解析工作,根据命名空间的不同进行不同标签的解析。其中,自定义标签BeanDefinitionParserDelegateparseCustomElement(Element ele, BeanDefinition containingBd) 方法来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// <1> 获取 namespaceUri
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// <2> 根据 namespaceUri 获取相应的 Handler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 调用自定义的 Handler 处理
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
getNamespaceURI()方法

先获取了NamespaceURI:

1
2
3
4
@Nullable
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}
getNamespaceHandlerResolver()方法

调用 XmlReaderContext.getNamespaceHandlerResolver() 方法,获得命名空间的解析器。

1
2
3
4
private final NamespaceHandlerResolver namespaceHandlerResolver;
public final NamespaceHandlerResolver getNamespaceHandlerResolver() {
return this.namespaceHandlerResolver;
}

NamespaceHandlerResolver的初始化:

在《注册BeanDefinitions》中提到注册BeanDefinitions时:

  • 首先,是通过XmlBeanDefinitionReadercreateBeanDefinitionDocumentReader()方法,获取 Document解析器BeanDefinitionDocumentReader实例。
  • 然后,调用BeanDefinitionDocumentReader实例的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法,进行注册。而该方法需要提供两个参数,一个是Document实例 doc,一个是XmlReaderContext实例 readerContext

readerContext 实例对象由XmlBeanDefinitionReadercreateReaderContext(Resource resource)方法创建。namespaceHandlerResolver 实例对象就是在这个时候初始化的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// XmlBeanDefinitionReader.java
//该方法最后一个参数为`NamespaceHandlerResolver`对象,通过`getNamespaceHandlerResolver()`方法获取
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}

//获取`NamespaceHandlerResolver`对象
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}

//可以看出`NamespaceHandlerResolver`对象类型默认为DefaultNamespaceHandlerResolver
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
return new DefaultNamespaceHandlerResolver(cl);
}
resolve()方法

由上可以看出this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)最终调用的就是DefaultNamespaceHandlerResolver类的resolve()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
// <1> 获取所有已经配置的 Handler 映射
Map<String, Object> handlerMappings = getHandlerMappings();
// <2> 根据 namespaceUri 获取 handler 的信息
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {//不存在返回null
return null;
}
//已经初始化过了直接强制类型转换
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
//未初始化
else {
String className = (String) handlerOrClassName;
try {
// <2> 根据 namespaceUri 获取 handler 的信息
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 初始化 NamespaceHandler 对象
namespaceHandler.init();
// 添加到缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}

获取所有已经配置的 Handler 映射,通过getHandlerMappings()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Resource location to search for.
* NamespaceHandler 映射配置文件地址
*/

private final String handlerMappingsLocation;
/**
* Stores the mappings from namespace URI to NamespaceHandler class name / instance.
* NamespaceHandler 映射。
*
* key:命名空间
* value:分成两种情况:
* 1)未初始化时,对应的 NamespaceHandler 的类路径;
* 2)已初始化,对应的 NamespaceHandler 对象
*/
@Nullable
private volatile Map<String, Object> handlerMappings;

private Map<String, Object> getHandlerMappings() {
//使用了延迟加载,双重检查锁
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isTraceEnabled()) {
logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
// 读取 handlerMappingsLocation
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded NamespaceHandler mappings: " + mappings);
}
// 初始化到 handlerMappings 中
handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}

执行完getHandlerMappings()获取了Handler映射后,之后还会执行NamespaceHandler.init()方法,该方法主要是将自定义标签解析器进行注册,调用的就是我们上边实例中的HelloNameSpaceHandler.init()方法,内部其实还是调用了父类NamespaceHandlerSupportregisterBeanDefinitionParser()来注册指定元素的BeanDefinitionParser解析器,其实就是将映射关系放在一个 Map 结构的 parsers 对象中。。

1
2
3
4
5
6
public class HelloNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("hello",new HelloDefinitionParser());
}
}

NamespaceHandlerSupportregisterBeanDefinitionParser()

1
2
3
4
5
6
7
8
private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<>();
/**
* key:元素名,不是命名空间名称
* value:对应 BeanDefinitionParser 的解析器
*/
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
this.decorators.put(elementName, dec);
}
parse()方法

执行完this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);获取了相应的Handler后,然后调用parse()方法执行自定义标签的解析。返回的NamespaceHandlerHandler是我们自己定义的HelloNameSpaceHandler,我们定义的继承了NamespaceHandlerSupport,故其实调用的是NamespaceHandlerSupport.parse()

1
2
3
4
5
6
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
  1. 调用 findParserForElement(Element element, ParserContext parserContext)方法,获取对应的 BeanDefinitionParser实例。实际上,其实就是获取在NamespaceHandlerSupportregisterBeanDefinitionParser() 方法里面注册的实例对象,注册是在我们执行init()的时候。
1
2
3
4
5
6
7
8
9
10
11
12
13
//NamespaceHandlerSupport.java
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 获得元素名
String localName = parserContext.getDelegate().getLocalName(element);
// 获得 BeanDefinitionParser 对象
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
  1. 最后执行parse(),得到的BeanDefinitionParser其实就是我们自己定义的HelloDefinitionParser继承了AbstractSingleBeanDefinitionParser,而AbstractSingleBeanDefinitionParser又继承了AbstractBeanDefinitionParser,所以最终执行的是该类的parse()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//`AbstractBeanDefinitionParser.java`
@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
// <1> 内部解析,返回 AbstractBeanDefinition 对象
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
// 解析 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);
}
// 解析 aliases 属性
String[] aliases = null;
if (shouldParseNameAsAliases()) {
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);
}
}
catch (BeanDefinitionStoreException ex) {
String msg = ex.getMessage();
parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
return null;
}
}
return definition;
}

核心在 parseInternal(Element element, ParserContext parserContext)方法。为什么这么说?因为该方法返回的是AbstractBeanDefinition对象。从前面默认标签的解析过程来看,我们就可以判断该方法就是将标签解析为AbstractBeanDefinition,且后续代码都是将 AbstractBeanDefinition转换为 BeanDefinitionHolder对象。所以真正的解析工作都交由parseInternal(Element element, ParserContext parserContext)方法来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// AbstractSingleBeanDefinitionParser.java
//解析元素为AbstractBeanDefinition对象
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
// 创建 BeanDefinitionBuilder 对象
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 获取父类元素
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
// 获取自定义标签中的 class,这个时候会去调用自定义解析中的 getBeanClass()
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
// beanClass 为 null,意味着子类并没有重写 getBeanClass() 方法,则尝试去判断是否重写了 getBeanClassName()
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
// 设置 source 属性
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
// 设置 scope 属性
BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
if (containingBd != null) {
// Inner bean definition must receive same scope as containing bean.
builder.setScope(containingBd.getScope());
}
// 设置 lazy-init 属性
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
builder.setLazyInit(true);
}
// 调用子类的 doParse() 进行解析
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}

总结

自定义标签解析的大致流程:

  • 首先,会加载 spring.handlers 文件,将其中内容进行一个解析,形成 <namespaceUri, 类路径> 这样的一个映射。
  • 然后,根据获取的 namespaceUri 就可以得到相应的类路径,对其进行初始化等到相应的 NamespaceHandler 对象。
  • 之后,调用该 NamespaceHandler 的 parse(...) 方法,在该方法中根据标签的 localName 得到相应的 BeanDefinitionParser 实例对象。
  • 最后,调用该 BeanDefinitionParser 的 parse(...) 方法。该方法定义在 AbstractBeanDefinitionParser 抽象类中,核心逻辑封装在其 parseInternal(...) 方法中,该方法返回一个 AbstractBeanDefinition 实例对象,其主要是在 AbstractSingleBeanDefinitionParser 中实现。对于自定义的 Parser 类,其需要实现 getBeanClass() 或者 getBeanClassName() 任一方法,和 doParse(...) 方法。

流程图:

解析自定义标签流程图


十三、解析bean标签子元素二
http://www.muzili.ren/2022/06/11/解析自定义标签二/
作者
jievhaha
发布于
2022年6月11日
许可协议