Spring (三) - 自定义标签的解析

Spring (三) - 自定义标签的解析

Spring (一) - XML 文件解析与 Bean 的注册 中,我们在最后讲到 Spring 中存在默认标签和自定义标签两种标签,在 上一节 Spring (一) - XML 文件解析与 Bean 的注册 中,我们分析了 Spring 对默认标签是如何解析的进行了分析,那么在这一节中我们将会着重讲述如何对自定义标签进行解析。

1. 自定义标签使用

这里以 Motan 为例来讲解。

1.1 定义 XSD 文件

定义一个 XSD 文件描述组件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- resources/META-INF/motan.xsd -->
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://api.weibo.com/schema/motan"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://api.weibo.com/schema/motan">

<xsd:element name="method">
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[ The method name (method.toString()). ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

<xsd:attribute name="requestTimeout" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The method invoke timeout. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

</xsd:complexType>
</xsd:element>

</xsd:schema>

声明命名空间:

定义复合元素:

定义元素属性:

CDATA:

A CDATA section is a section of element content that is marked for the parser to interpret as only character data, not markup.

1
2
3
4
5
6
7
8
9
10
11
12
13
<item>
<title>Title of Feed Item</title>
<link>/mylink/article1</link>
<description>
<![CDATA[
<p>
<a href="/mylink/article1"><img style="float: left; margin-right: 5px;" height="80" src="/mylink/image" alt=""/></a>
Author Names
<br/><em>Date</em>
<br/>Paragraph of text describing the article to be displayed</p>
]]>
</description>
</item>

1.2 创建一个类实现 BeanDefinitionParser 接口:

MotanBeanDefinitionParser 用来解析 XSD 文件中的定义和组件定义:

1
2
3
4
5
6
public class MotanBeanDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// ...
}
}

1.3 创建一个类实现 NamespaceHandlerSupport 接口:

MotanNamespaceHandler 负责将组件注册到 Spring 容器:

1
2
3
4
5
6
7
8
public class MotanNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("service", new MotanBeanDefinitionParser(ServiceConfigBean.class, true));
Initializable initialization = InitializationFactory.getInitialization();
initialization.init();
}
}

1.4 编写 spring.handlersspring.schemas 文件:

文件 resources/META-INF/spring.handlers:

1
http\://api.weibo.com/schema/motan=com.weibo.api.motan.config.springsupport.MotanNamespaceHandler

文件 resources/META-INF/spring.schemas:

1
http\://api.weibo.com/schema/motan.xsd=META-INF/motan.xsd

1.5 使用自定义标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:lang="http://www.springframework.org/schema/lang" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:motan="http://api.weibo.com/schema/motan" xmlns:weibo="http://api.weibo.com/schema/weibo"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
http://api.weibo.com/schema/weibo http://api.weibo.com/schema/weibo.xsd
http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd ">

<motan:referer id="clientDirectTest" registry="mockRegistry" directUrl="127.0.0.1:7888"
interface="com.weibo.api.motan.config.springsupport.ITest" protocol="motan"
application="test1" module="test1" />

</beans>

2 自定义标签解析

1
2
3
4
5
6
// BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

2.1 获取标签的命名空间

org.w3c.dom.Node 提供了方法可以直接获取元素的命名空间:

1
2
3
4
// BeanDefinitionParserDelegate.java
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}

2.2 提取自定义标签处理器

默认的 NamespaceHandlerResolver 是在类 XmlBeanDefinitionReader 创建 XmlReaderContext 中初始化的:

1
2
3
4
// XmlBeanDefinitionReader.java
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

所以 resolve 方法应该是在 DefaultNamespaceHandlerResolver 中实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// DefaultNamespaceHandlerResolver.java

@Override
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = getHandlerMappings();
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) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
}

2.2.1 getHandlerMappings 读取 Spring.handlers 配置文件并将配置文件缓存在 Map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// DefaultNamespaceHandlerResolver.java

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
Map<String, Object> handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
}
return this.handlerMappings;
}

2.2.2 根据命名空间寻找对应的信息:

1
2
3
4
5
6
7
8
9
10
// DefaultNamespaceHandlerResolver.java

Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
}

return handlerOrClassName;

2.3 标签解析

找到 NamespaceHandler 之后,我们这里实际上已经被初始化为 MotanNamespaceHandler 了,而 MotanNamespaceHandler 也已经被调用了 init() 方法完成了初始化的工作。因此就接着执行这句代码:

1
2
// BeanDefinitionParserDelegate.java
handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))

我们自己本身并没有定义 parse() 方法,因此应该在父类中定义了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// NamespaceHandlerSupport.java
private final Map<String, BeanDefinitionParser> parsers =
new HashMap<>();

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
return parser;
}

简而言之,就是从 parsers 中寻找到 MotanBeanDefinitionParser 实例,并调用其自身的 parse 方法进行进一步解析。

参考

推荐文章