Spring 系列教程之自定义标签的解析

时间:2022-09-06 23:47:37

Spring 系列教程之自定义标签的解析

在之前的章节中,我们提到了在 Spring 中存在默认标签与自定义标签两种,而在上一章节中我们分析了 Spring 中对默认标签的解析过程,相信大家一定已经有所感悟。那么,现在将开始新的里程,分析 Spring 中自定义标签的加载过程。同样,我们还是先再次回顾一下,当完成从配置文件到 Document 的转换并提取对应的 root 后,将开始了所有元素的解析,而在这一过程中便开始了默认标签与自定义标签两种格式的区分,函数如下:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

在本章中,所有的功能都是围绕其中的一句代码 delegate.parseCustomElement(root) 开展的。从上面的函数我们可以看出,当 Spring 拿到一个元素时首先要做的是根据命名空间进行解析,如果是默认的命名空间,则使用 parseDefaultElement() 方法进行元素解析,否则使用 parseCustomElement() 方法进行解析。在分析自定义标签的解析过程前,我们先了解一下自定义标签的使用过程。

4.1 自定义标签使用

在很多情况下,我们需要为系统提供可配置化支持,简单的做法可以直接基于 Spring 的标准 bean 来配置,但配置较为复杂或者需要更多丰富控制的时候,会显得非常笨拙。一般的做法会用原生态的方式去解析定义好的的 XML 文件,然后转化为配置对象。这种方式当然可以解决所有问题,但实现起来比较繁琐,特别是在配置非常复杂的时候,解析工作是一个不得不考虑的负担。 Spring 提供了可扩展 Schema 的支持,这是一个不错的折中方案,扩展 Spring 自定义标签配置大致需要以下几个步骤(前提是要把 Spring 的 core 包加入项目中):

(1) 创建一个需要扩展的组件。

(2) 定义一个 XSD 文件描述组件内容。

(3) 创建一个文件,实现 BeanDefinitionParser 接口,用来解析 XSD 文件中的定义和组件定义。

(4) 创建一个 Handler 文件,扩展自 NamespaceHandlerSupport,目的是将组件注册到 Spring 容器。

(5) 编写 Spring.handlers和 Spring.schemas 文件。

现在我们就按照上面的步骤带领读者一步步地体验自定义标签的过程。

(1) 首先我们创建一个普通的 POJO,这个 POJO 没有任何特别之处,只是用来接收配置文件。

public class User {
private String username;
private String email; // 省略 getter/setter 方法
}

(2) 定义一个 XSD 文件描述组件内容。

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://com.github.binarylei/schema/user"
xmlns:tns="http://com.github.binarylei/schema/user"
elementFormDefault="qualified"> <element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="userName" type="string"/>
<attribute name="email" type="string"/>
</complexType>
</element>
</schema>

在上面的 XSD 文件中描述了一个新的 targetNamespace,并在这个空间中定义了一个 name 为 user 的 element,user有3个属性 id、 username 和 email,其中 email 的类型为 string。这3个类主要用于验证 Spring 配置文件中自定义格式。XSD 文件是 XML DTD 的替代者,使用 XML Schema 语言进行编写,这里对 XSD Schema 不做太多解释,有兴趣的读者可以参考相关的资料

(3) 创创建一个文件,实现 BeanDefinitionParser 接口,用来解析 XSD 文件中的定义和组件定义

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    protected Class<?> getBeanClass(Element element) {
return User.class;
} protected void doParse(Element element, BeanDefinitionBuilder bean) {
String username = element.getAttribute("username");
String email = element.getAttribute("email"); // 将提取的数据放入到时 BeanDefinitionBuilder 中
// 待完成所有 bean 的解析后统一注册到 beanFactory
if (StringUtils.hasText(username)) {
bean.addPropertyValue("username", username);
} if (StringUtils.hasText(email)) {
bean.addPropertyValue("email", email);
}
}
}

(4) 创建一个 Handler 文件,扩展自 NamespaceHandlerSupport,目的是将组件注册到,目的是将组件注册到 Spring 容器。

public class NamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}

(5) 在工程的 META-INF 文件夹下新建 Spring.handlers 和 Spring.schemas 两个文件,内容如下:

到这里,自定义的配置就结束了,而 Spring 加载自定义的大致流程是遇到自定义标签然后就去 spring.handlers 和 spring.schemas 中去找对应的 handler 和 XSD,默认位置是 META-NF 下,进而有找到对应的 handler 以及解析元素的 Parser,从而完成了整个自定义元素的解析,也就是说自定义与 Spring 中默认的标准配置不同在于 Spring 将自定义标签解析的工作委托给了用户去实现。

(6) 创建测试配置文件,在配置文件中引入对应的命名空间以及 XSD 后,便可以直接使用自定义标签了。

<?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://com.github.binarylei/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://com.github.binarylei/schema/user http://com.github.binarylei/schema/user.xsd"> <myname:user id="testBean" userName="binarylei" email="binarylei@qq.com"/>
</beans>

(7) 测试

public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"spring/custom_bean/user.xml");
User test = (User) context.getBean("testBean");
System.out.println(test.getEmail());
}

不出意外的话,你应该看到了我们期待的结果,控制台上打印出:binarylei@qq.com。

在上面的例子中,我们实现了通过自定义标签实现了通过属性的方式将 user 类型的 Bean 赋值,在 Spring 中自定义标签非常常用,例如我们熟知的事务标签:tx(tx:annotation-driven)

4.2 自定义标签解析

了解了自定义标签的使用后,我们带着强烈的好奇心来探究一下自定义标签的解析过程。

public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
} // containingBd 为父类 bean,对顶层元素的解析就设置为 null
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
// 根据命名空间找到对应的 NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 调用自定义的 NamespaceHandler 进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

相信了解了自定义标签的使用方法后,或多或少会对自定义标签的实现过程有一个自己的想法。其实思路非常的简单,无非是根据对应的 bean 获取对应的命名空间,根据命名空间解析对应的处理器,然后根据用户自定义的处理器进行解析。可是有些事情说起来简单做起来难,我们先看看如何获取命名空间吧。

4.2.1 获取标签的命名空间

标签的解析是从命名空间的提起开始的,无论是区分 Spring 中默认标签和自定义标签还是区分自定义标签中不同标签的处理器都是以标签所提供的命名空间为基础的,而至于如何提取对应元素的命名空间其实并不需要我们亲自去实现,在 org.w3c.dom.Node 中已经提供了方法供我们直接调用:

public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}

4.2.2 提取自定义标签处理器

有了命名空间,就可以进行 NamespaceHandler 的提取了,继续之前的 parseCustomElement() 函数的跟踪,分析 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri),在 readerContext 初始化的日时候其属性 namespacehandlerresolver 已经被初始化为了 DefaultNamespaceHandlerreSolver 的实例,所以,这里调用的 resolve() 方法其实调用的是 Defaultnamespacehandlerresolver 类中的方法。我们进人 DefaultNamespaceHandlerreSolver 的 resolve() 方法进行查看。

@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;
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);
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);
}
}
}

Spring 系列教程之自定义标签的解析的更多相关文章

  1. Spring 系列教程之默认标签的解析

    Spring 系列教程之默认标签的解析 之前提到过 Spring 中的标签包括默认标签和自定义标签两种,而两种标签的用法以及解析方式存在着很大的不同,本章节重点带领读者详细分析默认标签的解析过程. 默 ...

  2. Spring源码学习-容器BeanFactory&lpar;四&rpar; BeanDefinition的创建-自定义标签的解析&period;md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  3. Spring 系列教程之容器的功能

    Spring 系列教程之容器的功能 经过前面几章的分析,相信大家已经对 Spring 中的容器功能有了简单的了解,在前面的章节中我们一直以 BeanFacotry 接口以及它的默认实现类 XmlBea ...

  4. 狗鱼IT教程&colon;推介最强最全的Spring系列教程

    Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建. 简单来说,Spring是一个分层的JavaSE/EEfull-stack( ...

  5. React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发

    React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发   2016/09/23 |  React Native技术文章 |  Sky丶清|  4 条评论 |  1 ...

  6. 基于Spring开发——自定义标签及其解析

    1. XML Schema 1.1 最简单的标签 一个最简单的标签,形式如: <bf:head-routing key="1" value="1" to= ...

  7. Spring源码学习——自定义标签

    2019独角兽企业重金招聘Python工程师标准>>> 1.自定义标签步骤 创建一个需要扩展的组件 定义xsd文件描述组件内容 创建一个文件,实现BeanDefinitionPars ...

  8. spring原理之四种基本标签的解析

    四种标签 在spring的配置文件中存在四种基本的标签分别是:beans,bean,import,alias 四种标签的功能: beans:定义一个单独的应用配置(测试配置,开发配置等),在服务器部署 ...

  9. Java Web开发技术教程入门-自定义标签

    回顾: 昨天了解了JSP开发的两种模式Model1和Model2模式.Model1采用JSP+JavaBean技术开发Web应用,它比较适合小规模应用的开发,效率较高,易于实现.但由于在Model1中 ...

随机推荐

  1. 仅使用处理单个数字的I&sol;O例程,编写一个过程以输出任意实数(可以是负的)

    题目取自:<数据结构与算法分析:C语言描述_原书第二版>——Mark Allen Weiss   练习1.3 如题. 补充说明:假设仅有的I/O例程只处理单个数字并将其输出到终端,我们将这 ...

  2. Inno Setup 打包工具总结

    Inno Setup 打包工具总结 分类: Install Setup 2013-02-02 15:44 2386人阅读 评论(0) 收藏 举报 最近打包用到了Inno setup,在这个过程中容易犯 ...

  3. 【转载】Spring中的applicationContext&period;xml与SpringMVC的xxx-servlet&period;xml的区别

    一直搞不明白两者的区别. 如果使用了SpringMVC,事实上,bean的配置完全可以在xxx-servlet.xml中进行配置.为什么需要applicationContext.xml?一定必须? 一 ...

  4. PC端使用opencv获取webcam,通过socket把Mat图像传输到android手机端

    demo效果图: PC端 android端 大体流程 android端是服务器端,绑定IP和端口,监听来自PC端的连接, pc端通过socket与服务器andorid端传输图片. 主要代码 andro ...

  5. 记一次Django报错Reverse for &&num;39&semi;indextwo&&num;39&semi; with no arguments not found&period; 1 pattern&lpar;s&rpar; tried&colon; &lbrack;&&num;39&semi;&dollar;index&sol;&dollar;&&num;39&semi;&rsqb;

    启动python manage.py runserver 打开127.0.0.1:8000,报错信息如下: Reverse for 'indextwo' with no arguments not f ...

  6. windows上的Qt 5的依赖部署打包

    通常我们编译Qt程序的时候最终会生成exe或dll,这些可执行文件都会有Qt模块的依赖,如果项目一旦庞大,就不是很好看出缺了什么模块,导致安装包安装到其他绿色干净的windows机器上会提示缺少XXX ...

  7. HTML5 Video&sol;Audio播放本地文件

    这段时间经常看到开发者在反复询问同一个问题,为什么通过设置src属性,不能播放本地的媒体文件?例如video.src=”D:\test.mp4”. 这是因为浏览器中的JavaScript不能直接直接访 ...

  8. 前端学习笔记之ES6快速入门

    0x1 let和const let ES6新增了let命令,用于声明变量.其用法类似var,但是声明的变量只在let命令所在的代码块内有效. { let x = 10; var y = 20; } x ...

  9. android 几个工具方法

    集合几个工具方法,方便以后使用. 1.获取手机 分辨率屏幕: public static void printScreenInfor(Context context){ DisplayMetrics ...

  10. python基础之函数名称空间与作用域