Spring中XML schema扩展机制的深入讲解
作者:徐靖峰 发布时间:2022-06-29 07:44:15
前言
很久没有写关于 Spring 的文章了,最近在系统梳理 Dubbo 代码的过程中发现了 XML schema 这个被遗漏的知识点。由于工作中使用 SpringBoot 比较多的原因,几乎很少接触 XML,此文可以算做是亡羊补牢,另一方面,也为后续的 Dubbo 源码解析做个铺垫。
XML schema 扩展机制是啥?从Spring2.0开始,Spring提供了XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义XML Bean解析器,并集成到Spring Ioc 容器中。这并不是一块很大的知识点,翻阅一下 Spring 的文档,我甚至没找到一个贯穿上下文的词来描述这个功能,XML Schema Authoring 是文档中对应的标题,简单来说:
Spring 为基于 XML 构建的应用提供了一种扩展机制,用于定义和配置 Bean。 它允许使用者编写自定义的 XML bean 解析器,并将解析器本身以及最终定义的 Bean 集成到 Spring IOC 容器中。
Dubbo 依赖了 Spring,并提供了一套自定义的 XML 标签,<dubbo:application> ,<dubbo:registry> ,<dubbo:protocol>,<dubbo:service>。作为使用者,大多数人只需要关心这些参数如何配置,但不知道有没有人好奇过,它们是如何加载进入 Spring 的 IOC 容器中被其他组件使用的呢?这便牵扯出了今天的主题:Spring 对 XML schema 的扩展支持。
自定义 XML 扩展
为了搞懂 Spring 的 XML 扩展机制,最直接的方式便是实现一个自定义的扩展。实现的步骤也非常简单,分为四步:
编写一个 XML schema 文件描述的你节点元素。
编写一个 NamespaceHandler 的实现类
编写一个或者多个 BeanDefinitionParser 的实现 (关键步骤).
注册上述的 schema 和 handler。
我们的目的便是想要实现一个 kirito XML schema,我们的项目中可以自定义 kirito.xml,在其中会以 kirito 为标签来定义不同的类,并在最终的测试代码中验证这些声明在 kirito.xml 的类是否被 Spring 成功加载。大概像这样,是不是和 dubbo.xml 的格式很像呢?
动手实现
有了明确的目标,我们逐步开展自己的工作。
1 编写kirito.xsd
resources/META-INF/kirito.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.cnkirito.moe/schema/kirito"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.cnkirito.moe/schema/kirito"> ①
<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:element name="service"> ②
<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>
① 注意这里的 targetNamespace="http://www.cnkirito.moe/schema/kirito"
这便是之后 kirito 标签的关键点。
② kirito.xsd 定义了两个元素: application 和 service,出于简单考虑,都只有一个 name 字段。
schema 的意义在于它可以和 eclipse/IDEA 这样智能化的集成开发环境形成很好的搭配,在编辑 XML 的过程中,用户可以获得告警和提示。 如果配置得当,可以使用自动完成功能让用户在事先定义好的枚举类型中进行选择。
2 编写KiritoNamespaceHandler
public class KiritoNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
super.registerBeanDefinitionParser("application", new KiritoBeanDefinitionParser(ApplicationConfig.class));
super.registerBeanDefinitionParser("service", new KiritoBeanDefinitionParser(ServiceBean.class));
}
}
完成 schema 之后,还需要一个 NamespaceHandler 来帮助 Spring 解析 XML 中不同命名空间的各类元素。
<kirito:application name="kirito"/>
<dubbo:application name="dubbo"/>
<motan:application name="motan"/>
不同的命名空间需要不同的 NamespaceHandler 来处理,在今天的示例中,我们使用 KiritoNamespaceHandler 来解析 kirito 命名空间。KiritoNamespaceHandler 继承自 NamespaceHandlerSupport 类,并在其 init() 方法中注册了两个 BeanDefinitionParser ,用于解析 kirito 命名空间/kirito.xsd 约束中定义的两个元素:application,service。BeanDefinitionParser 是下一步的主角,我们暂且跳过,将重心放在父类 NamespaceHandlerSupport 之上。
public interface NamespaceHandler {
void init();
BeanDefinition parse(Element element, ParserContext parserContext);
BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}
NamespaceHandlerSupport 是 NamespaceHandler 命名空间处理器的抽象实现,我粗略看了NamespaceHandler 的几个实现类,parse 和 decorate 方法可以完成元素节点的组装并通过 ParserContext 注册到 Ioc 容器中,但实际我们并没有调用这两个方法,而是通过 init() 方法注册 BeanDefinitionParser 来完成解析节点以及注册 Bean 的工作,所以对于 NamespaceHandler,我们主要关心 init 中注册的两个 BeanDefinitionParser 即可。
3 编写KiritoBeanDefinitionParser
在文章开始我们便标记到 BeanDefinitionParser 是最为关键的一环,每一个 BeanDefinitionParser 实现类都负责一个映射,将一个 XML 节点解析成 IOC 容器中的一个实体类。
public class KiritoBeanDefinitionParser implements BeanDefinitionParser {
private final Class<?> beanClass;
public KiritoBeanDefinitionParser(Class<?> beanClass) {
this.beanClass = beanClass;
}
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String name = element.getAttribute("name");
beanDefinition.getPropertyValues().addPropertyValue("name", name);
parserContext.getRegistry().registerBeanDefinition(name, beanDefinition);
return beanDefinition;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass);
}
}
由于我们的实体类是非常简单的,所以不存在很复杂的解析代码,而实际项目中,往往需要大量的解析步骤。parse 方法会解析一个个 XML 中的元素,使用 RootBeanDefinition 组装成对象,并最终通过 parserContext 注册到 IOC 容器中。
至此,我们便完成了 XML 文件中定义的对象到 IOC 容器的映射。
4 注册schema和handler
最后一步还需要通知 Spring,告知其自定义 schema 的所在之处以及对应的处理器。
resources/META-INF/spring.handlers
http\://www.cnkirito.moe/schema/kirito=moe.cnkirito.sample.xsd.KiritoNamespaceHandler
resources/META-INF/spring.schemas
http\://www.cnkirito.moe/schema/kirito/kirito.xsd=META-INF/kirito.xsd
没有太多可以说的,需要遵守 Spring 的约定。
至此一个自定义的 XML schema 便扩展完成了,随后来验证一下。
验证扩展
我们首先定义好 kirito.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:kirito="http://www.cnkirito.moe/schema/kirito"
xsi:schemaLocation=" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.cnkirito.moe/schema/kirito
http://www.cnkirito.moe/schema/kirito/kirito.xsd">
<kirito:application name="kirito-demo-application"/>
<kirito:service name="kirito-demo-service"/>
</beans>
使用 Spring 去加载它,并验证 IOC 容器中是否存在注册成功的 Bean。
@SpringBootApplication
@ImportResource(locations = {"classpath:kirito.xml"})
public class XmlSchemaAuthoringSampleApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(XmlSchemaAuthoringSampleApplication.class, args);
ServiceBean serviceBean = applicationContext.getBean(ServiceBean.class);
System.out.println(serviceBean.getName());
ApplicationConfig applicationConfig = applicationContext.getBean(ApplicationConfig.class);
System.out.println(applicationConfig.getName());
}
}
观察控制台的输出:
kirito-demo-service
kirito-demo-application
一个基础的基于 XML schema 的扩展便完成了。
Dubbo中的XML schema扩展
最后我们以 Dubbo 为例,看看一个成熟的 XML schema 扩展是如何被应用的。
刚好对应了四个标准的扩展步骤,是不是对 XML 配置下的 Dubbo 应用有了更好的理解了呢?
顺带一提,仅仅完成 Bean 的注册还是不够的,在“注册”的同时,Dubbo 还进行了一系列其他操作如:暴露端口,开启服务器,完成注册中心的注册,生成代理对象等等行为,由于不在本文的范围内,后续的 Dubbo 专题会专门介绍这些细节,本文便是了解 Dubbo 加载流程的前置文章了。
来源:https://www.cnkirito.moe/spring-xsd/
猜你喜欢
- 一、案例场景在使用 @Autowired 时,你或多或少都会遇过类似的错误:required a single bean, but 2 we
- 基于 kotlin/coroutine/retrofit/jetpack 打造,100来行代码,用法超级简单舒适设置默认Retrofit工厂
- feign传输List的坑无法直接传输List错误方法1@RequestMapping(value = "/stat/mercha
- 文章导读本系列文章介绍从0开始搭建一个基于分布式的医疗挂号系统。本次四篇文章完成了医院设置微服务模块的后端接口,为了方便开发,对接口的返回结
- 一、简单的命令-n 在非 GUI 模式下运行JMeter-t 要运行的 JMeter 测试脚本文件-r 远程执行启动全部代理机-H 代理机器
- 具体代码如下所示:public class MainActivity extends AppCompatActivity { p
- 使用ByteArrayOutputStream下载文件//文件名称String filepath = ServletActionContex
- C#连接本地.mdf文件:项目中右键点击,新增——数据——基于服务的数据库,项目下直接生成.mdf数据库文件,后台(数据库的写入用参数传递)
- HashMap和Hashtable的比较是Java面试中的常见问题,用来考验程序员是否能够正确使用集合类以及是否可以随机应变使用多种思路解决
- 一:简述如果我们想要生成一个随机数,通常会使用Random类。但是在并 * 况下Random生成随机数的性能并不是很理想,今天给大家介绍一下J
- spring boot框架中已经集成了redis,在1.x.x的版本时默认使用的jedis客户端,现在是2.x.x版本默认使用的lettuc
- 使用@Provider注意事项(要点)1.在Mapper接口和@InsertProvider方法类中,不要使用重载,也就是说,不要使用方法名
- Struts2Struts2是在WebWork2基础发展而来的。和struts1一样, Struts2也属于MVC框架。不过有一点
- 一、什么是组合模式定义:将对象以树形结构组织起来,以达成“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。动机(Mo
- 一、使用JDK生成WSDL的对象类1、cmd进入JDK的bin文件中执行命令 wsimport -keep -p com.demo.clie
- 背景:新需求需要引入新jar包,引入后发现本地启动没有报错,发到测试环境提示某个bean无法创建,nested exception is j
- 本文实例讲述了Android编程实现向桌面添加快捷方式的方法。分享给大家供大家参考,具体如下:有时候为了使用方便,需要在桌面上添加快捷方式,
- 最近在开发中遇到了这样一个问题,在下拉刷新组件中包含了一个轮播图组件,当左右滑动的图片时很容易触发下拉刷新,如下图所示:如图中红色箭头所示方
- 我就废话不多说了,大家还是直接看代码吧~Caused by: java.net.SocketException: Software caus
- 一、变量C#共有其中变量类型有:静态变量、实类变量、数组元素、数值参数、引用参数、输出参数和局部变量先定义一个简单的类来说明,如下:publ