好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

<?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

?

1

2

3

4

5

6

7

8

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 中不同命名空间的各类元素。

?

1

2

3

<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 之上。

?

1

2

3

4

5

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 容器中的一个实体类。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<?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。

?

1

2

3

4

5

6

7

8

9

10

11

12

@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/

查看更多关于Spring中XML schema扩展机制的深入讲解的详细内容...

  阅读:44次