基于Spring的可扩展Schema进行开发自定义配置标签支持

一、背景

  最近和朋友一起想开发一个类似alibaba dubbo的功能的工具,其中就用到了基于Spring的可扩展Schema进行开发自定义配置标签支持,通过上网查资料自己写了一个demo.今天在这里进行和大家分享,也记录下方便以后复习备忘。

二、demo测试环境

  1.JDK1.7

  2.spring 4.2.5.RELEASE

  3.基于Maven

  4.开发工具Eclipse

三、项目介绍

  1.实现步骤分析

    [1].设计配置属性并开发JavaBean.

    [2].编写xsd文件.

    [3].编写NamespaceHandler和BeanDefinitionParser完成解析工作.

    [4].编写spring.handlers和spring.schemas串联起所有部件.

    [5].编写名为application.xml的spring配置文件

    [6].Maven Java项目的pom.xml.

    [7].编写测试类进行测试.

  2.实现代码

    [1].设计配置属性并开发JavaBean代码(基于BaseBean进行定义)

BaseBean.java

1 package com.hafiz.zhang.tag.bean;
2 
3 public interface BaseBean {
4     public abstract void init() throws Exception;
5     public abstract void destory();
6 }

ApplicationBean.java

 1 package com.hafiz.zhang.tag.bean;
 2 
 3 public class ApplicationBean implements BaseBean {
 4     private String id;
 5     private String name;
 6     private String version;
 7     private String description;
 8     public String getId() {
 9         return id;
10     }
11     public void setId(String id) {
12         this.id = id;
13     }
14     public String getVersion() {
15         return version;
16     }
17     public void setVersion(String version) {
18         this.version = version;
19     }
20     public String getName() {
21         return name;
22     }
23     public void setName(String name) {
24         this.name = name;
25     }
26     public String getDescription() {
27         return description;
28     }
29     public void setDescription(String description) {
30         this.description = description;
31     }
32 
33     @Override
34     public String toString() {
35         return "ApplicationBean [id=" + id + ", name=" + name + ", version=" + version + ", description=" + description
36                 + "]";
37     }
38     @Override
39     public void init() throws Exception {
40         System.out.println("---------ApplicationBean init---------");
41     }
42 
43     @Override
44     public void destory() {
45         System.out.println("---------ApplicationBean destory---------");
46     }
47 
48 }

  [2].编写xsd文件.

hafiz.xsd(为上一步设计好的配置项编写XSD文件,XSD是schema的定义文件,配置的输入和解析输出都是以XSD为契约,本例中XSD如下:)

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <xsd:schema
 3     xmlns="http://www.hafiz.com/schema/hafiz"
 4     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 5     xmlns:beans="http://www.springframework.org/schema/beans"
 6     targetNamespace="http://www.hafiz.com/schema/hafiz"
 7     elementFormDefault="qualified"
 8     attributeFormDefault="unqualified">
 9     <xsd:import namespace="http://www.springframework.org/schema/beans"/>
10     <xsd:element name="application">
11         <xsd:complexType>
12             <xsd:complexContent>
13                 <xsd:extension base="beans:identifiedType">
14                     <xsd:attribute name="name" type="xsd:string"/>
15                     <xsd:attribute name="version" type="xsd:string"/>
16                     <xsd:attribute name="description" type="xsd:string"/>
17                 </xsd:extension>
18             </xsd:complexContent>
19         </xsd:complexType>
20     </xsd:element>    
21 </xsd:schema>

注意:1.关于xsd:schema的各个属性具体含义就不作过多解释,可以参见http://www.w3school.com.cn/schema/schema_schema.asp.

   2.<xsd:element name="application">对应着配置项节点的名称,因此在应用中会用application作为节点名来引用这个配置.

   3.<xsd:attribute name="name" type="xsd:string" />和<xsd:attribute name="version" type="xsd:string" />以及

    <xsd:attribute name="description" type="xsd:string" />对应着配置项application的三个属性名,因此在应用中可以配置name和version以及description三个属性,都是string类型。

   4.完成后需把xsd存放在classpath下,一般都放在META-INF目录下(本例就放在这个目录下)

  [3].编写NamespaceHandler和BeanDefinitionParser完成解析工作.

     下面需要完成解析工作,会用到NamespaceHandler和BeanDefinitionParser这两个概念。具体说来NamespaceHandler会根据schema和节点名找到某个BeanDefinitionParser,然后由BeanDefinitionParser完成具体的解析工作。因此需要分别完成NamespaceHandler和BeanDefinitionParser的实现类,Spring提供了默认实现类NamespaceHandlerSupport和BeanDefinitionParser,简单的方式就是去继承这两个类。本例就是采取这种方式:

HafizNamespaceHandler.java

 1 package com.hafiz.zhang.tag.handlers;
 2 
 3 import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
 4 
 5 import com.hafiz.zhang.tag.bean.ApplicationBean;
 6 import com.hafiz.zhang.tag.parser.ApplicationBeanDefinitionParser;
 7 
 8 /**
 9  * @author hafiz.Zhang
10  * @Date 2016年5月17日 下午12:22:57
11  * @Description 定义自定义的命名空间hafiz处理器
12  */
13 public class HafizNamespaceHandler extends NamespaceHandlerSupport {
14 
15     @Override
16     public void init() {
17         //在这里进行注册自定义命名空间
18         registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser(ApplicationBean.class));
19     }
20 
21 }

注:其中registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser());就是用来把节点名和解析类联系起来,在配置中引用application配置项时,就会用ApplicationBeanDefinitionParser来解析配置。

ApplicationBeanDefinitionParser.java

 1 package com.hafiz.zhang.tag.parser;
 2 
 3 import org.springframework.beans.factory.config.BeanDefinition;
 4 import org.springframework.beans.factory.support.RootBeanDefinition;
 5 import org.springframework.beans.factory.xml.BeanDefinitionParser;
 6 import org.springframework.beans.factory.xml.ParserContext;
 7 import org.springframework.util.StringUtils;
 8 import org.w3c.dom.Element;
 9 
10 /**
11  * @author hafiz.Zhang
12  * @Date 2016年5月17日 下午12:27:12
13  * @Description 在这里定义自定义命名空间的bean解析器
14  */
15 public class ApplicationBeanDefinitionParser implements BeanDefinitionParser {
16 
17     private Class<?> clazz;
18     public ApplicationBeanDefinitionParser(Class<?> clazz) {
19         this.clazz = clazz;
20     }
21     @Override
22     public BeanDefinition parse(Element element, ParserContext parserContext) {
23         RootBeanDefinition rbd = new RootBeanDefinition();
24         rbd.setBeanClass(clazz);
25         String id = element.getAttribute("id");
26         String name = element.getAttribute("name");
27         String version = element.getAttribute("version");
28         String desc = element.getAttribute("description");
29         if(StringUtils.hasText(id)) {
30             rbd.getPropertyValues().addPropertyValue("id", id);
31         }
32         if(StringUtils.hasText(name)) {
33             parserContext.getRegistry().registerBeanDefinition(name, rbd);//这句话非常重要,意思是基于名字进行把对应的bean加载到spring容器中
34             rbd.getPropertyValues().addPropertyValue("name", name);
35         }
36         if(StringUtils.hasText(version)) {
37             rbd.getPropertyValues().addPropertyValue("version", version);
38         }
39         if(StringUtils.hasText(desc)) {
40             rbd.getPropertyValues().addPropertyValue("description", desc);
41         }
42         rbd.setInitMethodName("init");
43         rbd.setDestroyMethodName("destory");
44         return rbd;
45     }
46 
47 }

注:其中element.getAttribute就是用配置中取得属性值,rbd.getPropertyValues().addPropertyValue就是把属性值放到bean中。

[4].编写spring.handlers和spring.schemas串联起所有部件

  上面几个步骤走下来会发现开发好的handler与xsd还没法让spring容器感知到,就这样放上去是没法把前面做的工作纳入体系中的,spring提供了spring.handlers和spring.schemas这两个配置文件来完成这项工作,这两个文件需要我们自己编写并放入META-INF文件夹中,这两个文件的地址必须是META-INF/spring.handlers和META-INF/spring.schemas,spring会默认去载入它们,本例中spring.handlers如下所示:

1 http\://www.hafiz.com/schema/hafiz=com.hafiz.zhang.tag.handlers.HafizNamespaceHandler

以上表示当使用到名为"http://www.hafiz.com/schema/hafiz"的schema引用时,会通过com.hafiz.zhang.tag.handlers.HafizNamespaceHandler来完成解析.

spring.schemas如下所示:

1 http\://www.hafiz.com/schema/hafiz.xsd=META-INF/hafiz.xsd

以上就是载入xsd文件。

注:以上两个文件中的"\"表示转义。

[5].编写名为application.xml的spring配置文件

  编写application.xml文件并放在classpath下,建议这样做,但不是必须放在该位置。

 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:hafiz="http://www.hafiz.com/schema/hafiz"
 5     xsi:schemaLocation="  
 6         http://www.springframework.org/schema/beans 
 7         http://www.springframework.org/schema/beans/spring-beans-4.2.xsd  
 8         http://www.hafiz.com/schema/hafiz 
 9         http://www.hafiz.com/schema/hafiz.xsd">
10 
11     <hafiz:application id="test_demo" name="appliationBean" version="1.1.0" description="这是我自动拓展spring的schema的测试demo"/>
12 
13 </beans>

其中xmlns:hafiz="http://www.hafiz.com/schema/hafiz"是用来指定自定义schema,xsi:schemaLocation用来指定xsd文件。

<hafiz:application id="test_demo" name="applicationBean" version="1.1.0" description="这是我自动拓展spring的schema的测试demo"/>是一个具体的自定义配置使用实例.

注:此处的hafiz不是不能改变的,只要使用和上面指定的“xmlns:标签名”的标签名一样就可以(代码加粗标黑处)。

[6].Maven Java项目的pom.xml.

在该文件中主要引入spring的依赖

 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 2   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 3   <modelVersion>4.0.0</modelVersion>
 4 
 5   <groupId>com.hafiz.zhang</groupId>
 6   <artifactId>springTag</artifactId>
 7   <version>0.0.1-SNAPSHOT</version>
 8   <packaging>jar</packaging>
 9 
10   <name>springTag</name>
11   <url>http://maven.apache.org</url>
12 
13   <properties>
14     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15     <spring.version>4.2.5.RELEASE</spring.version>
16   </properties>
17 
18   <dependencies>
19     <dependency>
20       <groupId>junit</groupId>
21       <artifactId>junit</artifactId>
22       <version>3.8.1</version>
23       <scope>test</scope>
24     </dependency>
25     <!-- springframework start -->
26     <dependency>
27         <groupId>org.springframework</groupId>
28         <artifactId>spring-core</artifactId>
29         <version>${spring.version}</version>
30     </dependency>
31     <dependency>
32         <groupId>org.springframework</groupId>
33         <artifactId>spring-context-support</artifactId>
34         <version>${spring.version}</version>
35     </dependency>
36     <!-- springframe end -->
37   </dependencies>
38 </project>

[7].编写测试类进行测试.

 1 package com.hafiz.zhang.test;
 2 
 3 import org.springframework.context.ApplicationContext;
 4 import org.springframework.context.support.ClassPathXmlApplicationContext;
 5 
 6 import com.hafiz.zhang.tag.bean.ApplicationBean;
 7 
 8 /**
 9  * @author hafiz.Zhang
10  * @Date 2016年5月17日 下午2:01:37
11  * @Description 在此类中进行测试自定义拓展的schema
12  */
13 public class ApplicationTest 
14 {
15     private static ApplicationContext ac;
16     public static void main( String[] args )
17     {
18         ac = new ClassPathXmlApplicationContext("application.xml");
19         ApplicationBean bean = (ApplicationBean)ac.getBean("appliationBean");
20         System.out.println( "配置文件中的bean为:" );
21         System.out.println( "id = " + bean.getId() );
22         System.out.println( "name = " + bean.getName() );
23         System.out.println( "version = " + bean.getVersion() );
24         System.out.println( "description = " + bean.getDescription() );
25     }
26 }

在控制台会输出:

附:项目结构图

到此为止,spring的自定义标签就已经实现了,欢迎大家进行交流学习~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏游戏杂谈

Unity的Input输入

Unity中的输入管理器由Input类进行操控。官方文档地址:https://docs.unity3d.com/ScriptReference/Input.ht...

28220
来自专栏腾讯云Elasticsearch Service

Elasitcsearch 底层系列 Lucene 内核解析之 Stored Fields

Lucene 的 stored fields 主要用于行存文档需要保存的字段内容,每个文档的所有 stored fields 保存在一起,在查询请求需要返回字段...

20310
来自专栏向治洪

android内存优化

刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成。其实Java中没有指针的概念,但...

25290
来自专栏james大数据架构

Android中Application的应用

当android程序启动时系统会创建一个 application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮...

20260
来自专栏软件开发

前端MVC Vue2学习总结(七)——ES6与Module模块化、Vue-cli脚手架搭建、开发、发布项目与综合示例

使用vue-cli可以规范项目,提高开发效率,但是使用vue-cli时需要一些ECMAScript6的知识,特别是ES6中的模块管理内容,本章先介绍ES6中的基...

16960
来自专栏自由而无用的灵魂的碎碎念

Tips in Visual Studio 2008

.NET几乎程序员都在使用visual studio 2008进行开发。可是,你通过它达到最大的开发效率了吗?

12720
来自专栏何俊林

金三银四,Android高级开发面试题目,帮你助力

最近金三银四,相信不少朋友都在跃跃欲动,看看市场机会,连夜整理了一波Android高级开发面试题目,帮你助力! Java基础 1、内部类的作用 内部类可以用多个...

406100
来自专栏项勇

笔记 33 | Android通信之Thread类实现多线程

21350
来自专栏不会写文章的程序员不是好厨师

Spring源码初探-IOC(2)-Bean的初始化-自定义标签解析

前面一文介绍的是Spring对于DefaultElement的解析,例如bean/import/alias等,但是在Spring体系中也存在很多扩展标签,例如事...

8530
来自专栏智能大石头

关于自定义控件设计时如何把属性写入aspx中的研究(上)

如何通过继承GridView来修改在设计时绑定数据源时自动生成的ASP.Net代码? 具体情况是这样的,ObjectDataSource绑定到实体类,Grid...

24380

扫码关注云+社区

领取腾讯云代金券