Spring中XML schema扩展机制的深入讲解

前言

很久没有写关于 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 加载流程的前置文章了。

总结:

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 浅谈XML Schema中的elementFormDefault属性

    elementFormDefault属性与命名空间相关,其值可设置为qualified或unqualified 如果设置为qualified: 在XML文档中使用局部元素时,必须使用限定短名作为前缀 sean.xsd: <?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:sean=&

  • 一个简单的XML Schema的例子

    我们可以看到,DTD的语法相当复杂,并且它不符合XML文件的标准,自成一个体系.也就是说DTD文档本身并不是一个良好形式的XML文档,上面的关于DTD的介绍也仅仅是作了一个简介,目的是帮助大家能读懂DTD文件以及在必要时创建简单的DTD文件,因为现在很多的XML应用是建立在DTD之上的. 另外一个代替DTD的就是W3C定义的Schema,Schema从字面意义上来说,可以翻译成模式.大纲.计划.规划等等.它的基本意思就是说为XML文档制定一种模式. Schema相对于DTD的明显好处

  • Spring中XML schema扩展机制的深入讲解

    前言 很久没有写关于 Spring 的文章了,最近在系统梳理 Dubbo 代码的过程中发现了 XML schema 这个被遗漏的知识点.由于工作中使用 SpringBoot 比较多的原因,几乎很少接触 XML,此文可以算做是亡羊补牢,另一方面,也为后续的 Dubbo 源码解析做个铺垫. XML schema 扩展机制是啥?从Spring2.0开始,Spring提供了XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义XML Bean解析器,并集成到Spring Ioc

  • Spring XML Schema扩展机制的使用示例

    前言 在当前Java生态,Spring算的上是最核心的框架,所有的开发组件想要得到大范围更便捷的使用,都要和Spring进行整合,比如我们熟知的Mybatis.Dubbo等,以及内部封装的各类组件包括Redis.MQ.配置中心等. 有了整合这一步,我们只需引入相应的jar,比如mybatis-spring,然后进行简单的配置后即可在Spring工程中使用Mybatis的功能,也正是由于这样的便捷性,导致很多时候我们没有对其进行深究. XML Schema扩展 打开mybatis-spring.d

  • 关于Spring自定义XML schema 扩展的问题(Spring面试高频题)

    引言 自从SpringBoot时代的到来,去除了Spring的各种繁琐的XML配置,让我们可以腾出双手以便于更加专注的搬砖.记得那时候刚学Spring的时候,每天被Spring的各种XMl配置文件折磨的不行,每引入一个新的框架,最担心的就是jar冲突.哪个配置文件又配的不对.配置文件没有起作用.所以每次搭建好一个项目就把配置文件用小笔记记录下来, 方便下次在整合项目的时候直接copy复制就好.下面我们就以Spring整合dubbo的事例看下 <beans xmlns:xsi="http:/

  • Spring中Xml属性配置的解析全过程记录

    1 工程概述 1.1 pom文件 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.ver

  • Spring中xml配置文件的基础使用方式详解

    目录 1. xml配置文件的读取 1.1 通过类路径读取配置文件 1.2 通过文件系统绝对路径读取配置文件 1.3使用BeanFactory接口读取配置文件 2.带参构造对象的创建(constructor-arg标签) 3.使用另一个类中的方法创建对象,并放到Spring容器中 4.调用另一个类中的静态方法创建对象,并放到Spring容器中 5.对象的生命周期 6.单例多例的测试 1. xml配置文件的读取 目录结构 applicationContext.xml配置文件 <?xml versio

  • Spring中自定义Schema如何解析生效详解

    前言 随着 Spring Boot 的日渐流行,应用里的大部分配置都被隐藏了起来,我们仅需要关心真正的业务内容, Controller, Service, Repository,拿起键盘就是一通业务代码的Coding,具体的 Component Scan,View,PlaceHolder ... 都可以抛在脑后.但其实这种零配置在 Java 应用开发中,还真不太久. 「由奢入俭难」,不少开发者都经历过 Spring XML 配置的冗长,再回到这种配置确实不好受. 但有些时候,由于配置的内容在 S

  • 在Spring中使用JDBC和JDBC模板的讲解

    spring dao层中对jdbc进行了封装,使用模板模式的设计模式,通过ioc被动注入的方式将jdbcTemplate这个模板类注入到数据对象中,进行数据库操作. 我们要在一个类中进行CRUD操作(crud主要被用在描述软件系统中数据库或者持久层的基本操作功能.),首先要将jdbcTemplate这个模板类注入到数据对象类中,然后将DataSource这个类注入到jdbcTemplate,获取数据源. 这样数据对象类就可以通过jdbcTemplate类中的方法进行数据库操作了. 注意:这里需要

  • Spring的Xml和JavaConfig 扩展哪个好用

    引言 上一篇文章我们有怎么介绍到如何通过XML的形式来定义Spring的扩展<Spring面试高频题如何:自定义XML schema 扩展>,好多人都在吐槽现在都什么年代了,xml还有人再用吗?这玩意早就过时了吧,还有必要去掌握它吗?Spring官网都把这种方式放在最后面了,可想而知它的重要性到底怎样了?既然大家都吐槽了,那我们今天继续来介绍下基于注解的Spring扩展. JavaConfig 配置扩展 从Spring3.0开始Spring提供了JavaConfig的方式可以用来代替以前XML

  • 详解JAVA Spring 中的事件机制

    说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎么实现的呢?说listener之前,我们先从设计模式开始讲起. 观察者模式 观察者模式一般包含以下几个对象: Subject:被观察的对象.它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify().目标类可以是接口,也可以是抽象类或具体类. ConcreteSubject:具体的

  • SpringBoot之自定义Schema扩展方式

    目录 SpringBoot 自定义Schema扩展 1. 配置ServiceConfig属性 2. 编写XSD文件 3. 编写NamespaceHandler 4. 编写BeanDefinitionParser 5. 配置spring.handlers和spring.schemas 6. 配置spring的加载文件 7. 测试 Spring Schema扩展机制 1. 概述 2. 步骤 3. 示例如下 SpringBoot 自定义Schema扩展 最近在写RPC框架时,用到了自定义的xsd配置.

随机推荐