Spring 2.0可扩展XML配置初探[转]

本文通过一个简单的例子,说明如何去扩展XML配置,它大致需要的几个步骤。具体的需求是使用自定义标签定义一个简单的bean,这个bean有一个或多个属性,标签定义完成后,可以在其他项目中用自定义标签来定义该bean。


Spring 2.0版本支持扩展XML配置,着实兴奋了一下,在我看来,Spring作为目前最流行的框架,不能扩展用户自定义的配置,实在是Spring的一个很不爽的地方,的方式用起来比较通用,起码到目前为止符合大部分人的使用习惯,并且能完成Spring所有的配置操作,但是对于第三方的提供商或则会经常扩展Spring功能的开发者来说,使用这样的配置方式或许不是他们最想要的,他们需要使组件的配置更加直观、易阅读、易扩展……试想使用下面的配置方式。
 1<mytag:datasource id="datasource"
 2                  databaseType="oracle"
 3                  ip="192.168.1.110"
 4                  port="1521"
 5                  databaseName="myDB"
 6                  userName="admin"
 7                  password="password" />
 8
 9<mytag:ehCache id="ehcache"
10               cache="true"
11               maxElements="100000"
12               timeToIdleSeconds="120"
13               timeToLiveSeconds="120"
14               overflowToDisk="true" />

 上面的代码中配置了两个组件,datasource和cache组件,相比普通的bean&propertiy方式,很显然,这种配置方式更直观,更易读,更重要的是,如果作为组件发布,使用者也可以很方便快捷的开始使用,而不需要关心组件的任何实现细节。

扩展XML配置大致需要一下几个步骤:

1、创建一个需要扩展的组件
2、定义一个xsd文件描述组件内容
3、创建一个文件,实现BeanDefinitionParser接口,用来解析xsd文件中的定义和组件定义
4、创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器
5、编写spring.handlers和spring.schemas文件

提供一个简单的例子,来说明如何去扩展一个Spring配置,需求如下:使用自定义标签定义一个简单的bean,这个bean有一个或多个属性,标签定义完成后,可以在其他项目中用自定义标签来定义该bean。

首先,创建一个需要扩展的组件,在这里只是一个简单的bean,而且这个bean只有一个属性age。

One.java
 1package com.mysite.tag;
 2
 3public class One {
 4    private String age;
 5
 6    public One() {
 7    }
 8
 9    public String getAge() {
10        return age;
11    }
12
13    public void setAge(String age) {
14        this.age = age;
15    }
16}

 然后,建立一个xsd文件,来描述这个bean。

mytag.xsd(Unmi 注:创建在 src/com/mysite/tag 目录中)
 1<?xml version="1.0" encoding="UTF-8"?>
 2<xsd:schema xmlns="http://www.mysite.org/schema/mytag"
 3 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
 4 targetNamespace="http://www.mysite.org/schema/mytag"
 5 elementFormDefault="qualified" attributeFormDefault="unqualified">
 6 <xsd:import namespace="http://www.springframework.org/schema/beans" />
 7 <xsd:element name="one">
 8  <xsd:complexType>
 9   <xsd:complexContent>
10    <xsd:extension base="beans:identifiedType">
11     <xsd:attribute name="age" type="xsd:string" default="99999" />
12    </xsd:extension>
13   </xsd:complexContent>
14  </xsd:complexType>
15 </xsd:element>
16</xsd:schema>

Unmi 注:这里的 mytag.xsd 也可以写成如下的形式,简单些:
 1<?xml version="1.0" encoding="UTF-8"?>
 2<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 3        targetNamespace="http://www.mysite.org/schema/mytag" xmlns:tag="http://www.mysite.org/schema/mytag"
 4        elementFormDefault="qualified">
 5    <xsd:element name="one">
 6        <xsd:complexType>
 7            <xsd:attribute name="id" type="xsd:string" use="required" form="unqualified" />
 8            <xsd:attribute name="age" type="xsd:string" default="99999" />
 9        </xsd:complexType>
10    </xsd:element>
11</xsd:schema>

 在上面的xsd文件描述了一个新的targetNamespace,并在这个空间中定义了一个name为one的element,one有一个age属性,类型为string,默认值为99999.xsd文件是xml DTD的替代者,使用XML Schema语言进行编写,这里对xsd schema不做太多解释,有兴趣可以参考相关的资料。

创建一个Java文件,该文件实现了BeanDefinitionParser接口,用来解析xsd文件中的定义并注册到组件中。

MyBeanDefinitionParser.java
 1package com.mysite.tag;
 2
 3import org.springframework.beans.factory.config.BeanDefinition;
 4import org.springframework.beans.factory.config.BeanDefinitionHolder;
 5import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
 6import org.springframework.beans.factory.support.RootBeanDefinition;
 7import org.springframework.beans.factory.xml.BeanDefinitionParser;
 8import org.springframework.beans.factory.xml.ParserContext;
 9import org.w3c.dom.Element;
10
11public class MyBeanDefinitionParser implements BeanDefinitionParser {
12    public BeanDefinition parse(Element element, ParserContext parserContext) {
13        RootBeanDefinition def = new RootBeanDefinition();
14
15        // 设置Bean Class
16        def.setBeanClass(One.class);
17
18        // 注册默认ID属性
19        String id = element.getAttribute("id");
20        BeanDefinitionHolder idHolder = new BeanDefinitionHolder(def, id);
21        BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, parserContext.getRegistry());
22
23        // 注册age属性
24        String age = element.getAttribute("age");
25        BeanDefinitionHolder ageHolder = new BeanDefinitionHolder(def, age);
26        BeanDefinitionReaderUtils.registerBeanDefinition(ageHolder, parserContext.getRegistry());
27
28        def.getPropertyValues().addPropertyValue("age", age);
29
30        return def;
31    }
32}

 上面的代码仅仅实现了一个方法public BeanDefinition parse(Element element, ParserContext parserContext),设置相关的Bean Class,解析了对应的xsd文件,并将解析的内容注册到上下文中,同时返回一个BeanDefinition对象(BeanDefinition是Spring的bean定义,提供了bean部分的操作方法,如isSingleton()、isLazyInit()等)。

注意,id属性是一个默认的属性,可以不在xsd文件中描述,但是需要注册它,否则将无法通过getBean方法获取标签定义的bean,也无法被其他bean引用。

另外,下面代码是给bean的属性赋值,这个是不可缺少的,否则在使用标签定义时将无法获取bean属性的值。
1def.getPropertyValues().addPropertyValue("age", age);

 然后为组件编写一个Handler文件,扩展自NamespaceHandlerSupport,它的作用是将组件注册到Spring容器。

MyNameSpaceHandler.java
1package com.mysite.tag;
2
3import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
4
5public class MyNameSpaceHandler extends NamespaceHandlerSupport {
6    public void init() {
7        registerBeanDefinitionParser("one", new MyBeanDefinitionParser());
8    }
9}

 实际执行的过程只有一句代码,注册了一个名字为one的扩展配置,包含了MyBeanDefinitionParser所parser相关xsd的内容。

到了这里,一个Spring扩展标签已经完成,但是我们目前还没办法使用它,Spring没那么聪明,它无法知道我们在什么地方定义了哪些扩展标签,这些标签将被谁解析,这个过程要我们通过一些文件来告知Spring知道,它们就是spring.handlers和spring.schemas,它们放在META-INF目录中,Spring.jar的META-INF目录中也有同名的文件,它们的文件内容基本上是相似的,而Spring在执行过程中,如果发现其他jar文件的META-INF文件夹中包含有这两个文件,Spring将会合并它们。

Unmi 注:如果不进行打包,只在当前项目中直接测试本程序,可把以下两文件放在项目根目录下的 META-INF 文件夹中。

spring.schemas
1http\://www.mysite.org/schema/mytag=com.mysite.tag.MyNameSpaceHandler

 spring.handlers
1http\://www.mysite.org/schema/mytag.xsd=com/mysite/tag/mytag.xsd

 而spring.schemas将告诉Spring配置文件知道,如果在配置中引用http://www.mysite.org/schema/mytag.xsd,它应该去什么地方找相应的xsd文件。

而spring.handlers文件将告诉Spring,应该使用哪个Handler注册扩展标签。

现在为止,一个完整的xml扩展标签全部完成,做一个小应用测试一下。

将整个项目打包成jar文件(别忘记META-INF内的两个文件),然后新建一个项目,引用刚才打包的jar文件,并引用Spring相关文件。

需要注意,自定义xml扩展配置只有xsd方式的引用才可以使用。

application.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" xmlns:tag="http://www.mysite.org/schema/mytag"
4     xsi:schemaLocation="http://www.springframework.org/schema/beans
5         http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
6         http://www.mysite.org/schema/mytag http://www.mysite.org/schema/mytag.xsd">
7    <tag:one id="oneBean" age="99"/>
8</beans>

 在xml文件引用上可以看到,配置了一个名字为tag的名称空间,目标为http://www.mysite.org/schema/mytag命名空间,这个目标名称空间必须是存在于项目的引用中的(mytag.xsd中所定义的)。

1<tag:one id="oneBean" age="99"/>

上面定义了一个id为oneBean的组件,使用了“one”扩展标签,也就是我们在handler中所注册的,组件age属性的值为99。

TestClient.java(Unmi 注:直接在当前项目中运行本代码即可,不一定要打成 jar 包在别的项目中使用)
 1package cc.unmi.spring;
 2
 3import org.springframework.beans.factory.BeanFactory;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6import com.mysite.tag.One;
 7
 8/**
 9 * 测试客户端
10 *
11 * @author Unmi
12 */
13public class TestClient {
14
15    public static void main(String[] args) {
16        BeanFactory context = new ClassPathXmlApplicationContext("applicationContext.xml");
17        One one = (One) context.getBean("oneBean");
18        System.out.print(one+":Age=>"+one.getAge());
19    }
20}

 运行测试程序,结果如下:
1com.mysite.tag.One@406199:Age=>99

 Spring的xml扩展是一个非常有用的特性,在Spring2.0的版本中也提供了一些新的标签使用,如,等,但就目前来讲受大家关注程度并不高,我想大部分使用Spring的开发人员都在使用Spring开发企业应用,而Spring所提供的定义的方式对于开发人员来说已经能够满足需要,但是如果看的更远一些,在Spring以后的发展过程中,xml扩展应该会成为spring的核心特性之一,或许到时大家会习惯这样的方式来编写Spring配置文件吧!
1<XXCompany:XXXModule id="">
2    ...
3    ...

 原文链接:http://developer.51cto.com/art/200707/51780.htm

参考:1. 关于spring 2.0自定义xml 标记