Spring中的XML schema扩展机制

Easter79
• 阅读 659

Spring中的XML schema扩展机制

前言

很久没有写关于 Spring 的文章了,最近在系统梳理 Dubbo 代码的过程中发现了 XML schema 这个被遗漏的知识点。由于工作中使用 SpringBoot 比较多的原因,几乎很少接触 XML,此文可以算做是亡羊补牢,另一方面,也为后续的 Dubbo 源码解析做个铺垫。

XML schema 扩展机制是啥?这并不是一块很大的知识点,翻阅一下 Spring 的文档,我甚至没找到一个贯穿上下文的词来描述这个功能, XMLSchemaAuthoring 是文档中对应的标题,简单来说:

Spring 为基于 XML 构建的应用提供了一种扩展机制,用于定义和配置 Bean。 它允许使用者编写自定义的 XML bean 解析器,并将解析器本身以及最终定义的 Bean 集成到 Spring IOC 容器中。

Spring中的XML schema扩展机制

Dubbo 依赖了 Spring,并提供了一套自定义的 XML 标签, <dubbo:application> , <dubbo:registry> , <dubbo:protocol>, <dubbo:service>。作为使用者,大多数人只需要关心这些参数如何配置,但不知道有没有人好奇过,它们是如何加载进入 Spring 的 IOC 容器中被其他组件使用的呢?这便牵扯出了今天的主题:Spring 对 XML schema 的扩展支持。

自定义 XML 扩展

为了搞懂 Spring 的 XML 扩展机制,最直接的方式便是实现一个自定义的扩展。实现的步骤也非常简单,分为四步:

  1. 编写一个 XML schema 文件描述的你节点元素。

  2. 编写一个 NamespaceHandler 的实现类

  3. 编写一个或者多个 BeanDefinitionParser 的实现 (关键步骤).

  4. 注册上述的 schema 和 handler。

我们的目的便是想要实现一个 kirito XML schema,我们的项目中可以自定义 kirito.xml,在其中会以 kirito 为标签来定义不同的类,并在最终的测试代码中验证这些声明在 kirito.xml 的类是否被 Spring 成功加载。大概像这样,是不是和 dubbo.xml 的格式很像呢?

Spring中的XML schema扩展机制

动手实现

有了明确的目标,我们逐步开展工作。

1 编写kirito.xsd

resources/META-INF/kirito.xsd

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <xsd:schema xmlns="http://www.cnkirito.moe/schema/kirito"

  3. xmlns:xsd="http://www.w3.org/2001/XMLSchema"

  4. xmlns:beans="http://www.springframework.org/schema/beans"

  5. targetNamespace="http://www.cnkirito.moe/schema/kirito"> ①

  6. <xsd:import namespace="http://www.springframework.org/schema/beans"/>

  7. <xsd:element name="application"> ②

  8. <xsd:complexType>

  9. <xsd:complexContent>

  10. <xsd:extension base="beans:identifiedType">

  11. <xsd:attribute name="name" type="xsd:string" use="required"/>

  12. </xsd:extension>

  13. </xsd:complexContent>

  14. </xsd:complexType>

  15. </xsd:element>

  16. <xsd:element name="service"> ②

  17. <xsd:complexType>

  18. <xsd:complexContent>

  19. <xsd:extension base="beans:identifiedType">

  20. <xsd:attribute name="name" type="xsd:string" use="required"/>

  21. </xsd:extension>

  22. </xsd:complexContent>

  23. </xsd:complexType>

  24. </xsd:element>

  25. </xsd:schema>

① 注意这里的 targetNamespace="http://www.cnkirito.moe/schema/kirito" 这便是之后 kirito 标签的关键点。

② kirito.xsd 定义了两个元素: application 和 service,出于简单考虑,都只有一个 name 字段。

schema 的意义在于它可以和 eclipse/IDEA 这样智能化的集成开发环境形成很好的搭配,在编辑 XML 的过程中,用户可以获得告警和提示。 如果配置得当,可以使用自动完成功能让用户在事先定义好的枚举类型中进行选择。

2 编写KiritoNamespaceHandler

  1. public class KiritoNamespaceHandler extends NamespaceHandlerSupport {

  2. @Override

  3. public void init() {

  4. super.registerBeanDefinitionParser("application", new KiritoBeanDefinitionParser(ApplicationConfig.class));

  5. super.registerBeanDefinitionParser("service", new KiritoBeanDefinitionParser(ServiceBean.class));

  6. }

  7. }

完成 schema 之后,还需要一个 NamespaceHandler 来帮助 Spring 解析 XML 中不同命名空间的各类元素。

  1. <kirito:application name="kirito"/>

  2. <dubbo:application name="dubbo"/>

  3. <motan:application name="motan"/>

不同的命名空间需要不同的 NamespaceHandler 来处理,在今天的示例中,我们使用 KiritoNamespaceHandler 来解析 kirito 命名空间。KiritoNamespaceHandler 继承自 NamespaceHandlerSupport 类,并在其 init() 方法中注册了两个 BeanDefinitionParser ,用于解析 kirito 命名空间/kirito.xsd 约束中定义的两个元素:application,service。BeanDefinitionParser 是下一步的主角,我们暂且跳过,将重心放在父类 NamespaceHandlerSupport 之上。

  1. public interface NamespaceHandler {

  2. void init();

  3. BeanDefinition parse(Element element, ParserContext parserContext);

  4. BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);

  5. }

NamespaceHandlerSupport 是 NamespaceHandler 命名空间处理器的抽象实现,我粗略看了NamespaceHandler 的几个实现类,parse 和 decorate 方法可以完成元素节点的组装并通过 ParserContext 注册到 Ioc 容器中,但实际我们并没有调用这两个方法,而是通过 init() 方法注册 BeanDefinitionParser 来完成解析节点以及注册 Bean 的工作,所以对于 NamespaceHandler,我们主要关心 init 中注册的两个 BeanDefinitionParser 即可。

3 编写KiritoBeanDefinitionParser

在文章开始我们便标记到 BeanDefinitionParser 是最为关键的一环,每一个 BeanDefinitionParser 实现类都负责一个映射,将一个 XML 节点解析成 IOC 容器中的一个实体类。

  1. public class KiritoBeanDefinitionParser implements BeanDefinitionParser {

  2. private final Class<?> beanClass;

  3. public KiritoBeanDefinitionParser(Class<?> beanClass) {

  4. this.beanClass = beanClass;

  5. }

  6. private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass) {

  7. RootBeanDefinition beanDefinition = new RootBeanDefinition();

  8. beanDefinition.setBeanClass(beanClass);

  9. beanDefinition.setLazyInit(false);

  10. String name = element.getAttribute("name");

  11. beanDefinition.getPropertyValues().addPropertyValue("name", name);

  12. parserContext.getRegistry().registerBeanDefinition(name, beanDefinition);

  13. return beanDefinition;

  14. }

  15. @Override

  16. public BeanDefinition parse(Element element, ParserContext parserContext) {

  17. return parse(element, parserContext, beanClass);

  18. }

  19. }

由于我们的实体类是非常简单的,所以不存在很复杂的解析代码,而实际项目中,往往需要大量的解析步骤。parse 方法会解析一个个 XML 中的元素,使用 RootBeanDefinition 组装成对象,并最终通过 parserContext 注册到 IOC 容器中。

至此,我们便完成了 XML 文件中定义的对象到 IOC 容器的映射。

4 注册schema和handler

最后一步还需要通知 Spring,告知其自定义 schema 的所在之处以及对应的处理器。

resources/META-INF/spring.handlers

  1. http\://www.cnkirito.moe/schema/kirito=moe.cnkirito.sample.xsd.KiritoNamespaceHandler

resources/META-INF/spring.schemas

  1. http\://www.cnkirito.moe/schema/kirito/kirito.xsd=META-INF/kirito.xsd

没有太多可以说的,需要遵守 Spring 的约定。

至此一个自定义的 XML schema 便扩展完成了,随后来验证一下。

验证扩展

我们首先定义好 kirito.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4. xmlns:kirito="http://www.cnkirito.moe/schema/kirito"

  5. xsi:schemaLocation=" http://www.springframework.org/schema/beans

  6. http://www.springframework.org/schema/beans/spring-beans.xsd

  7. http://www.cnkirito.moe/schema/kirito

  8. http://www.cnkirito.moe/schema/kirito/kirito.xsd">

  9. <kirito:application name="kirito-demo-application"/>

  10. <kirito:service name="kirito-demo-service"/>

  11. </beans>

使用 Spring 去加载它,并验证 IOC 容器中是否存在注册成功的 Bean。

  1. @SpringBootApplication

  2. @ImportResource(locations = {"classpath:kirito.xml"})

  3. public class XmlSchemaAuthoringSampleApplication {

  4. public static void main(String[] args) {

  5. ConfigurableApplicationContext applicationContext = SpringApplication.run(XmlSchemaAuthoringSampleApplication.class, args);

  6. ServiceBean serviceBean = applicationContext.getBean(ServiceBean.class);

  7. System.out.println(serviceBean.getName());

  8. ApplicationConfig applicationConfig = applicationContext.getBean(ApplicationConfig.class);

  9. System.out.println(applicationConfig.getName());

  10. }

  11. }

观察控制台的输出:

kirito-demo-service kirito-demo-application

一个基础的基于 XML schema 的扩展便完成了。

Dubbo中的XML schema扩展

最后我们以 Dubbo 为例,看看一个成熟的 XML schema 扩展是如何被应用的。

Spring中的XML schema扩展机制

刚好对应了四个标准的扩展步骤,是不是对 XML 配置下的 Dubbo 应用有了更好的理解了呢?

顺带一提,仅仅完成 Bean 的注册还是不够的,在“注册”的同时,Dubbo 还进行了一系列其他操作如:暴露端口,开启服务器,完成注册中心的注册,生成代理对象等等行为,由于不在本文的范围内,后续的 Dubbo 专题会专门介绍这些细节,本文便是了解 Dubbo 加载流程的前置文章了。

喜欢这篇文章的,欢迎关注Kirito的微信公众号:「Kirito的技术分享」。

Spring中的XML schema扩展机制

关注微信公众号

欢迎加入咖啡拿铁公众号的微信群,这里可以进行各种技术交流,由于人数满了可以搜索我的微信号lizhao5803588,备注加群

本文分享自微信公众号 - 咖啡拿铁(close_3092860495)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k