前言
在 上一节 spring解密 - 默认标签的解析 中,重点分析了 spring 对默认标签是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签进行解析。话不多说了,来一起看看详细的介绍吧。
自定义标签
在讲解 自定义标签解析 之前,先看下如何自定义标签
定义 xsd 文件
定义一个 xsd 文件描述组件内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?xml version= "1.0" encoding= "utf-8" ?>
<xsd:schema xmlns= "http://www.battcn.com/schema/battcn" xmlns:xsd= "http://www.w3.org/2001/xmlschema" xmlns:beans= "http://www.springframework.org/schema/beans" targetnamespace= "http://www.battcn.com/schema/battcn"
elementformdefault= "qualified"
attributeformdefault= "unqualified" >
<xsd: import namespace= "http://www.springframework.org/schema/beans" />
<xsd:element name= "application" >
<xsd:complextype>
<xsd:complexcontent>
<xsd:extension base= "beans:identifiedtype" >
<xsd:attribute name= "name" type= "xsd:string" use= "required" />
</xsd:extension>
</xsd:complexcontent>
</xsd:complextype>
</xsd:element>
</xsd:schema>
|
- 声明命名空间: 值得注意的是 xmlns 与 targetnamespace 可以是不存在,只要映射到指定 xsd 就行了。
- 定义复合元素: 这里的 application 就是元素的名称,使用时 <battcn:application id="battcn"/>
- 定义元素属性: 元素属性就是 attribute 标签,我们声明了一个必填的 name 的属性,使用时 <battcn:application id="battcn" name="levin"/>
定义解析规则
1.创建一个类实现 beandefinitionparser 接口(也可继承 spring 提供的类),用来解析 xsd 文件中的定义和组件定义
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class applicationbeandefinitionparser extends abstractsinglebeandefinitionparser {
@override
protected class getbeanclass(element element) {
// 接收对象的类型 如:string name = (string) context.getbean("battcn");
return string. class ;
}
@override
protected void doparse(element element, beandefinitionbuilder bean) {
// 在 xsd 中定义的 name 属性
string name = element.getattribute( "name" );
bean.addconstructorargvalue(name);
}
}
|
这里创建了一个 applicationbeandefinitionparser 继承 abstractsinglebeandefinitionparser(是:beandefinitionparser 的子类), 重点就是重写的 doparse,在这个里面解析 xml 标签的,然后将解析出的 value(levin) 通过构造器方式注入进去
2.创建一个类继承 namespacehandlersupport 抽象类
1
2
3
4
5
6
|
public class battcnnamespacehandler extends namespacehandlersupport {
@override
public void init() {
registerbeandefinitionparser( "application" , new applicationbeandefinitionparser());
}
}
|
battcnnamespacehandler 的作用特别简单,就是告诉 spring 容器,标签 <battcn:application />
应该由那个解析器解析(这里是我们自定义的:applicationbeandefinitionparser),负责将组件注册到 spring 容器
3.编写 spring.handlers 和 spring.schemas 文件
文件存放的目录位于 resources/meta-inf/文件名
spring.handlers
1
|
http\: //www.battcn.com/schema/battcn=com.battcn.handler.battcnnamespacehandler
|
spring.schemas
1
|
http\: //www.battcn.com/schema/battcn.xsd=battcn.xsd
|
4.使用自定义标签
申明 bean.xml 文件,定义如下
1
2
3
4
5
6
7
|
<?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:battcn= "http://www.battcn.com/schema/battcn" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http: //www.battcn.com/schema/battcn
http: //www.battcn.com/schema/battcn.xsd">
<battcn:application id= "battcn" name= "levin" />
</beans>
|
创建一个测试类,如果看到控制台输出了 levin 字眼,说明自定义标签一切正常
1
2
3
4
5
6
7
|
public class application {
public static void main(string[] args) {
applicationcontext context = new classpathxmlapplicationcontext( "bean.xml" );
string name = (string) context.getbean( "battcn" );
system.out.println(name);
}
}
|
5.如图所示
源码分析
自定义标签解析入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class beandefinitionparserdelegate {
@nullable
public beandefinition parsecustomelement(element ele, @nullable beandefinition containingbd) {
// 获取命名空间地址 http://www.battcn.com/schema/battcn
string namespaceuri = getnamespaceuri(ele);
if (namespaceuri == null ) {
return null ;
}
// namespacehandler 就是 自定义的 battcnnamespacehandler 中注册的 application
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));
}
}
|
与默认标签解析规则一样的是,都是通过 getnamespaceuri(node node)
来获取命名空间,那么 this.readercontext.getnamespacehandlerresolver()
是从哪里获取的呢?我们跟踪下代码,可以发现在项目启动的时候,会在 xmlbeandefinitionreader 将所有的 meta-inf/spring.handles 文件内容解析,存储在 handlermappers(一个concurrenthashmap) 中,在调用 resolve(namespaceuri)
校验的时候在将缓存的内容提取出来做对比
1
2
3
4
5
6
7
8
|
public class xmlbeandefinitionreader {
public namespacehandlerresolver getnamespacehandlerresolver() {
if ( this .namespacehandlerresolver == null ) {
this .namespacehandlerresolver = createdefaultnamespacehandlerresolver();
}
return this .namespacehandlerresolver;
}
}
|
resolve
1.加载指定的 namespacehandler 映射,并且提取的 namespacehandler 缓存起来,然后返回
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
|
public class defaultnamespacehandlerresolver {
@override
@nullable
public namespacehandler resolve(string namespaceuri) {
map<string, object> handlermappings = gethandlermappings();
// 从 handlermappings 提取 handlerorclassname
object handlerorclassname = handlermappings.get(namespaceuri);
if (handlerorclassname == null ) {
return null ;
}
else if (handlerorclassname instanceof namespacehandler) {
return (namespacehandler) handlerorclassname;
}
else {
string classname = (string) handlerorclassname;
try {
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);
// handler 初始化
namespacehandler.init();
handlermappings.put(namespaceuri, namespacehandler);
return namespacehandler;
}
catch (classnotfoundexception ex) {
throw new fatalbeanexception( "namespacehandler class [" + classname + "] for namespace [" +
namespaceuri + "] not found" , ex);
}
catch (linkageerror err) {
throw new fatalbeanexception( "invalid namespacehandler class [" + classname + "] for namespace [" +
namespaceuri + "]: problem with handler class file or dependent class" , err);
}
}
}
}
|
标签解析
加载完 namespacehandler 之后,battcnnamespacehandler 就已经被初始化为 了,而 battcnnamespacehandler 也调用了 init()
方法完成了初始化的工作。因此就接着执行这句代码: handler.parse(ele, new parsercontext(this.readercontext, this, containingbd));
具体标签解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class namespacehandlersupport {
@override
@nullable
public beandefinition parse(element element, parsercontext parsercontext) {
beandefinitionparser parser = findparserforelement(element, parsercontext);
return (parser != null ? parser.parse(element, parsercontext) : null );
}
@nullable
private beandefinitionparser findparserforelement(element element, parsercontext parsercontext) {
// 解析出 <battcn:application /> 中的 application
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;
}
}
|
简单来说就是从 parsers 中寻找到 applicationbeandefinitionparser 实例,并调用其自身的 doparse 方法进行进一步解析。最后就跟解析默认标签的套路一样了…
总结
熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走…
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
说点什么
全文代码:https://gitee.com/battcn/battcn-spring-source/tree/master/chapter2
原文链接:http://blog.battcn.com/2018/01/12/spring/spring-3/