Spring的XML扩展特性:让Spring加载和解析你自定义的XML文件
您目前处于:技术核心竞争力  2014-10-02

Spring 框架从 2.0 版本开始,提供了基于 Schema 风格的 XML 扩展机制,允许开发者扩展最基本的 Spring 配置文件(一般是 classpath 下的 spring.xml)。

试想一下,如果我们直接在 spring.xml 中加入一个自定义标签,会发生什么呢?Spring 框架启动的时候会报错,因为 Spring 根本不认识我们自定义的,这样对 spring.xml 的校验就会失败,最终结果就是框架不能启动。有什么方法,能够让 spring 认识并加载解析我们自定义的呢?这就是 Spring 提供的 xml 扩展机制。我们可以在 spring.xml 中加入自己的标签,之后 Spring 会帮我们解析并纳入自己的管理范围内,这也就是说我们扩展了spring的功能。

现在我们来看下怎么实现这个功能,可以参考 Spring 帮助文档中的 extensible-xml.html。我们知道如果在需要在 spring.xml 中配置数据源,需要进行如下的配置:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">       
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />      
    <property name="url" value="jdbc:mysql://localhost:3309/sampledb" />      
    <property name="username" value="root" />      
    <property name="password" value="1234" />      
</bean>

这种方式配置虽然也比较简单,但是有一个缺点:使用标签不够明显,不如元素属性那么直接。现在我们希望在 spring.xml 中做如下的配置,就能够完成数据源的配置。

<aty:datasource id="myDataSourcce" 
url="jdbc:mysql://localhost:3309/demodb" userName="root" password="root" />

这种方式比较直接,配置不容易出错。如果让 spring 能够解析这个标签,需要4步。

1. 提供一个 xsd 文件,负责对 xml 的标签进行校验

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.aty.com/schema/aty" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:beans="http://www.springframework.org/schema/beans"
	targetNamespace="http://www.aty.com/schema/aty" 
	elementFormDefault="qualified"
	attributeFormDefault="unqualified">

	<xsd:import namespace="http://www.springframework.org/schema/beans" />

	<xsd:element name="datasource">
		<xsd:complexType>
			<xsd:complexContent>
				<xsd:extension base="beans:identifiedType">
					<xsd:attribute name="url" type="xsd:string" use="required" />
					<xsd:attribute name="userName" type="xsd:string" use="required" />
					<xsd:attribute name="password" type="xsd:string" use="required" />
				</xsd:extension>
			</xsd:complexContent>
		</xsd:complexType>
	</xsd:element>

</xsd:schema>

2. 定义一个 BeanDefinitionParser 负责解析 xml,并将必要的信息放入 spring 中

package net.aty.custom.define;

import net.aty.custom.cfg.DataSourceInfo;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class DatasourceBeanDefinitionParser implements BeanDefinitionParser {
	public BeanDefinition parse(Element element, ParserContext context) {
		RootBeanDefinition def = new RootBeanDefinition();

		// 设置Bean Class
		def.setBeanClass(DataSourceInfo.class);

		// 注册ID属性
		String id = element.getAttribute("id");
		BeanDefinitionHolder idHolder = new BeanDefinitionHolder(def, id);
		BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, context.getRegistry());

		// 注册属性
		String url = element.getAttribute("url");
		String userName = element.getAttribute("userName");
		String password = element.getAttribute("password");

		BeanDefinitionHolder urlHolder = new BeanDefinitionHolder(def, url);
		BeanDefinitionHolder userNameHolder = new BeanDefinitionHolder(def, userName);
		BeanDefinitionHolder passwordHolder = new BeanDefinitionHolder(def, password);

		BeanDefinitionReaderUtils.registerBeanDefinition(urlHolder, context.getRegistry());
		BeanDefinitionReaderUtils.registerBeanDefinition(userNameHolder, context.getRegistry());
		BeanDefinitionReaderUtils.registerBeanDefinition(passwordHolder, context.getRegistry());

		def.getPropertyValues().addPropertyValue("url", url);
		def.getPropertyValues().addPropertyValue("userName", userName);
		def.getPropertyValues().addPropertyValue("password", password);

		return def;
	}
}

该类的功能:设置相关的 BeanClass,解析了对应的 xsd 文件,并将解析的内容注册到上下文中,同时返回一个 BeanDefinition 对象(BeanDefinition 是 Spring 的 bean 定义,提供了 bean 部分的操作方法,如 isSingleton()、isLazyInit() 等)。注意:id 属性是一个默认的属性,可以不在 xsd 文件中描述,但是需要注册它,否则将无法通过 getBean 方法获取标签定义的 bean,也无法被其他 bean 引用。

3. 定义个 NamespaceHandler,由 Sping 框架的调用入口。这也是我们自定义 xml 解析的入口

package net.aty.custom.define;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class DatasourceNamespaceHandlerSupport extends NamespaceHandlerSupport {
	@Override
	public void init() {
		registerBeanDefinitionParser("datasource", new DatasourceBeanDefinitionParser());
	}
}

4. 配置 schema 和 handler

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

spring.handlers 内容如下:

http\://www.aty.com/schema/aty=net.aty.custom.define.DatasourceNamespaceHandlerSupport

spring.schemas 内容如下:

http\://www.aty.com/schema/aty.xsd=aty.xsd

至此扩展 xml 完成,我们可以进行测试了。可以通过 eclipse 将 spring_customDefine_xml 工程导出成 jar,然后再别的工程中进程测试即可。

测试工程的 spring.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:aty="http://www.aty.com/schema/aty"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
           http://www.aty.com/schema/aty
           http://www.aty.com/schema/aty.xsd">

	<aty:datasource id="myDataSourcce" 
	url="jdbc:mysql://localhost:3309/demodb" userName="root" password="root" />

</beans>

测试类代码如下:

import net.aty.custom.cfg.DataSourceInfo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestMain {
	private static ClassPathXmlApplicationContext context = 
	                        new ClassPathXmlApplicationContext("spring.xml");

	public static void main(String[] args) {
		DataSourceInfo d = (DataSourceInfo) context.getBean("myDataSourcce");
		System.out.println(d);
	}
}

转载请并标注: “本文转载自 linkedkeeper.com ”  ©著作权归作者所有