欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring源码解密之自定义标签与解析

程序员文章站 2023-12-16 23:49:22
前言 在 上一节 spring解密 - 默认标签的解析 中,重点分析了 spring 对默认标签是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签进行解析。...

前言

在 上一节 spring解密 - 默认标签的解析 中,重点分析了 spring 对默认标签是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签进行解析。话不多说了,来一起看看详细的介绍吧。

自定义标签

在讲解 自定义标签解析 之前,先看下如何自定义标签

定义 xsd 文件

定义一个 xsd 文件描述组件内容

<?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 文件中的定义和组件定义

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 抽象类

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

http\://www.battcn.com/schema/battcn=com.battcn.handler.battcnnamespacehandler

spring.schemas

http\://www.battcn.com/schema/battcn.xsd=battcn.xsd

4.使用自定义标签

申明 bean.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: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 字眼,说明自定义标签一切正常

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.如图所示

Spring源码解密之自定义标签与解析

源码分析

自定义标签解析入口

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) 校验的时候在将缓存的内容提取出来做对比

public class xmlbeandefinitionreader {
 public namespacehandlerresolver getnamespacehandlerresolver() {
 if (this.namespacehandlerresolver == null) {
 this.namespacehandlerresolver = createdefaultnamespacehandlerresolver();
 }
 return this.namespacehandlerresolver;
 }
}

resolve

1.加载指定的 namespacehandler 映射,并且提取的 namespacehandler 缓存起来,然后返回

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)); 具体标签解。

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

上一篇:

下一篇: