对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

XML Schema创作

从2.0版本开始,Spring提供了一种机制,可以在基本的Spring XML格式中添加基于模式的扩展来定义和配置bean。本节介绍了如何编写自己的自定义XML bean定义解析器,并将这些解析器集成到Spring IoC容器中。spring-doc.cadn.net.cn

为了方便使用具有模式感知功能的XML编辑器编写配置文件,Spring的可扩展XML配置机制基于XML Schema。如果您不熟悉Spring标准发行版中附带的当前XML配置扩展,您应该首先阅读关于XML Schema的前一节。spring-doc.cadn.net.cn

要创建新的XML配置扩展:spring-doc.cadn.net.cn

  1. 作者 一个XML模式来描述您的自定义元素。spring-doc.cadn.net.cn

  2. 代码 a custom NamespaceHandler implementation.spring-doc.cadn.net.cn

  3. 代码 一个或多个 BeanDefinitionParser 实现 (这是实际工作完成的地方)。spring-doc.cadn.net.cn

  4. 注册你的新构件到Spring。spring-doc.cadn.net.cn

为了一个统一的示例,我们创建一个XML扩展(一个自定义的XML元素),它允许我们配置SimpleDateFormat类型(来自java.text包)的对象。完成后,我们将能够如下定义SimpleDateFormat类型的bean定义:spring-doc.cadn.net.cn

<myns:dateformat id="dateFormat"
	pattern="yyyy-MM-dd HH:mm"
	lenient="true"/>

我们在本附录后面提供了更详细的示例。这个第一个简单示例的目的是引导您完成制作自定义扩展的基本步骤。spring-doc.cadn.net.cn

编写模式

创建用于与Spring的IoC容器一起使用的XML配置扩展首先需要编写一个XML Schema来描述该扩展。在我们的示例中,我们使用以下模式来配置SimpleDateFormat对象:spring-doc.cadn.net.cn

<!-- myns.xsd (inside package org/springframework/samples/xml) -->

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

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

	<xsd:element name="dateformat">
		<xsd:complexType>
			<xsd:complexContent>
				<xsd:extension base="beans:identifiedType"> (1)
					<xsd:attribute name="lenient" type="xsd:boolean"/>
					<xsd:attribute name="pattern" type="xsd:string" use="required"/>
				</xsd:extension>
			</xsd:complexContent>
		</xsd:complexType>
	</xsd:element>
</xsd:schema>
1 指定的行包含所有可识别标签的扩展基类(意味着它们有一个id属性,我们可以将其用作容器中的bean标识符)。我们可以使用此属性,因为我们导入了Spring提供的beans命名空间。

上述模式允许我们在XML应用程序上下文文件中直接配置SimpleDateFormat对象,使用<myns:dateformat/>元素,如下例所示:spring-doc.cadn.net.cn

<myns:dateformat id="dateFormat"
	pattern="yyyy-MM-dd HH:mm"
	lenient="true"/>

请注意,创建了基础设施类之后,前面的XML片段基本上与下面的XML片段相同:spring-doc.cadn.net.cn

<bean id="dateFormat" class="java.text.SimpleDateFormat">
	<constructor-arg value="yyyy-MM-dd HH:mm"/>
	<property name="lenient" value="true"/>
</bean>

前两个代码片段中的第二个 在容器中创建了一个bean(通过名称 dateFormat 标识,类型为 SimpleDateFormat),并设置了一些属性。spring-doc.cadn.net.cn

基于模式的方法来创建配置格式允许与具有模式感知XML编辑器的IDE紧密集成。通过使用适当编写的模式,您可以使用自动完成功能让用户在枚举中定义的多个配置选项之间进行选择。

编写一个NamespaceHandler

除了模式,我们还需要一个 NamespaceHandler 来解析 Spring 在解析配置文件时遇到的此命名空间中的所有元素。在这个示例中,NamespaceHandler 应该处理 myns:dateformat 元素的解析。spring-doc.cadn.net.cn

The NamespaceHandler 接口具有三个方法:spring-doc.cadn.net.cn

  • init(): 允许初始化 NamespaceHandler,并在 Spring 使用处理器之前调用。spring-doc.cadn.net.cn

  • BeanDefinition parse(Element, ParserContext): 当Spring遇到顶级元素时调用(不嵌套在bean定义或不同命名空间内)。此方法本身可以注册bean定义,返回一个bean定义,或两者兼而有之。spring-doc.cadn.net.cn

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext): 当Spring遇到不同命名空间的属性或嵌套元素时调用。 装饰一个或多个bean定义(例如)Spring支持的作用域。 我们首先通过一个简单的示例来突出显示,不使用装饰,然后在稍微复杂一些的示例中展示装饰的使用。spring-doc.cadn.net.cn

虽然你可以为整个命名空间编写自己的NamespaceHandler(因此提供解析命名空间中每个元素的代码),但在Spring XML配置文件中,通常情况下每个顶级XML元素都会生成一个bean定义(正如在我们的例子中,一个<myns:dateformat/>元素生成一个SimpleDateFormat bean定义)。Spring提供了许多便利类来支持这种场景。在下面的例子中,我们使用了NamespaceHandlerSupport类:spring-doc.cadn.net.cn

package org.springframework.samples.xml;

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

public class MyNamespaceHandler extends NamespaceHandlerSupport {

	public void init() {
		registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
	}
}
package org.springframework.samples.xml

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

class MyNamespaceHandler : NamespaceHandlerSupport {

	override fun init() {
		registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
	}
}

您可能会注意到,这个类中实际上并没有太多的解析逻辑。确实,NamespaceHandlerSupport 类有一个内置的委托概念。它支持注册任意数量的 BeanDefinitionParser 实例,当需要解析其命名空间中的元素时,它会将这些任务委托给这些实例。这种职责分离使得 NamespaceHandler 可以处理其命名空间中所有自定义元素的解析,而将 XML 解析的具体工作委托给 BeanDefinitionParsers 来完成。这意味着每个 BeanDefinitionParser 只包含解析单个自定义元素的逻辑,如我们在下一步中所见。spring-doc.cadn.net.cn

使用 BeanDefinitionParser

一个 BeanDefinitionParser 用于当 NamespaceHandler 遇到已映射到特定 bean 定义解析器的 XML 元素类型(在这种情况下为 dateformat)。换句话说,BeanDefinitionParser 负责解析模式中定义的一个特定顶级 XML 元素。在解析器中,我们可以访问 XML 元素(以及其子元素)以便解析我们的自定义 XML 内容,如以下示例所示:spring-doc.cadn.net.cn

package org.springframework.samples.xml;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

import java.text.SimpleDateFormat;

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { (1)

	protected Class getBeanClass(Element element) {
		return SimpleDateFormat.class; (2)
	}

	protected void doParse(Element element, BeanDefinitionBuilder bean) {
		// this will never be null since the schema explicitly requires that a value be supplied
		String pattern = element.getAttribute("pattern");
		bean.addConstructorArgValue(pattern);

		// this however is an optional property
		String lenient = element.getAttribute("lenient");
		if (StringUtils.hasText(lenient)) {
			bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
		}
	}

}
1 我们使用Spring提供的AbstractSingleBeanDefinitionParser来处理很多 创建单个BeanDefinition的基本工作。
2 我们为AbstractSingleBeanDefinitionParser超类提供我们的单个BeanDefinition表示的类型。
package org.springframework.samples.xml

import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
import org.springframework.util.StringUtils
import org.w3c.dom.Element

import java.text.SimpleDateFormat

class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { (1)

	override fun getBeanClass(element: Element): Class<*>? { (2)
		return SimpleDateFormat::class.java
	}

	override fun doParse(element: Element, bean: BeanDefinitionBuilder) {
		// this will never be null since the schema explicitly requires that a value be supplied
		val pattern = element.getAttribute("pattern")
		bean.addConstructorArgValue(pattern)

		// this however is an optional property
		val lenient = element.getAttribute("lenient")
		if (StringUtils.hasText(lenient)) {
			bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
		}
	}
}
1 我们使用Spring提供的AbstractSingleBeanDefinitionParser来处理很多 创建单个BeanDefinition的基本工作。
2 我们为AbstractSingleBeanDefinitionParser超类提供我们的单个BeanDefinition表示的类型。

在这个简单的例子中,我们只需要做这些。我们的单个BeanDefinition的创建由AbstractSingleBeanDefinitionParser超类处理,提取和设置bean定义的唯一标识符也是如此。spring-doc.cadn.net.cn

注册处理器与模式

编码已经完成。剩下要做的就是让Spring XML解析基础设施识别我们的自定义元素。我们通过在两个特殊用途的属性文件中注册我们的自定义namespaceHandler和自定义XSD文件来实现这一点。这些属性文件都放置在应用程序的META-INF目录中,并且可以与二进制类一起打包到JAR文件中。Spring XML解析基础设施会自动通过消费这些特殊用途的属性文件来识别你的新扩展,这些文件的格式将在接下来的两个部分中详细介绍。spring-doc.cadn.net.cn

编写META-INF/spring.handlers

名为spring.handlers的属性文件包含XML Schema URI到命名空间处理器类的映射。对于我们的示例,我们需要编写以下内容:spring-doc.cadn.net.cn

http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler

(在 Java 属性格式中,: 字符是一个有效的分隔符,因此 URI 中的 : 字符需要用反斜杠进行转义。)spring-doc.cadn.net.cn

键值对的第一部分(键)是与您的自定义命名空间扩展关联的URI,必须与targetNamespace属性的值完全匹配,如在您的自定义XSD模式中指定的。spring-doc.cadn.net.cn

编写 'META-INF/spring.schemas'

名为spring.schemas的属性文件包含XML Schema位置的映射(在使用该模式的XML文件中,这些位置与模式声明一起被引用,并作为xsi:schemaLocation属性的一部分)。此文件用于防止Spring必须使用默认的EntityResolver,这需要通过互联网访问来检索模式文件。如果你在此属性文件中指定了映射,Spring会在类路径资源中查找模式文件(在这种情况下,myns.xsdorg.springframework.samples.xml包中)。以下是我们需要添加的自定义模式的行:spring-doc.cadn.net.cn

http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd

(请记住,必须转义 : 字符。)spring-doc.cadn.net.cn

鼓励您将XSD文件(或文件)与NamespaceHandlerBeanDefinitionParser类一起部署在类路径上。spring-doc.cadn.net.cn

在您的Spring XML配置中使用自定义扩展

使用你自己实现的自定义扩展与使用Spring提供的“自定义”扩展没有什么不同。以下示例在Spring XML配置文件中使用了前几步中开发的自定义<dateformat/>元素:spring-doc.cadn.net.cn

<?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:myns="http://www.mycompany.example/schema/myns"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">

	<!-- as a top-level bean -->
	<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> (1)

	<bean id="jobDetailTemplate" abstract="true">
		<property name="dateFormat">
			<!-- as an inner bean -->
			<myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
		</property>
	</bean>

</beans>
1 我们的自定义bean。

更多详细示例

本节介绍了一些更详细的自定义XML扩展示例。spring-doc.cadn.net.cn

在自定义元素中嵌套自定义元素

本节中的示例展示了如何编写满足以下配置目标所需的各种工件:spring-doc.cadn.net.cn

<?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:foo="http://www.foo.example/schema/component"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">

	<foo:component id="bionic-family" name="Bionic-1">
		<foo:component name="Mother-1">
			<foo:component name="Karate-1"/>
			<foo:component name="Sport-1"/>
		</foo:component>
		<foo:component name="Rock-1"/>
	</foo:component>

</beans>

前一个配置将自定义扩展相互嵌套。实际上由<foo:component/>元素配置的类是Component类(在下一个示例中显示)。请注意Component类没有为components属性暴露setter方法。这使得通过setter注入配置Component类的bean定义变得困难(或者说是不可能的)。以下列出了Component类:spring-doc.cadn.net.cn

package com.foo;

import java.util.ArrayList;
import java.util.List;

public class Component {

	private String name;
	private List<Component> components = new ArrayList<Component> ();

	// there is no setter method for the 'components'
	public void addComponent(Component component) {
		this.components.add(component);
	}

	public List<Component> getComponents() {
		return components;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
package com.foo

import java.util.ArrayList

class Component {

	var name: String? = null
	private val components = ArrayList<Component>()

	// there is no setter method for the 'components'
	fun addComponent(component: Component) {
		this.components.add(component)
	}

	fun getComponents(): List<Component> {
		return components
	}
}

典型解决方案是创建一个自定义的FactoryBean,该自定义FactoryBean暴露了一个用于设置components属性的setter属性。以下示例展示了这样一个自定义的FactoryBeanspring-doc.cadn.net.cn

package com.foo;

import org.springframework.beans.factory.FactoryBean;

import java.util.List;

public class ComponentFactoryBean implements FactoryBean<Component> {

	private Component parent;
	private List<Component> children;

	public void setParent(Component parent) {
		this.parent = parent;
	}

	public void setChildren(List<Component> children) {
		this.children = children;
	}

	public Component getObject() throws Exception {
		if (this.children != null && this.children.size() > 0) {
			for (Component child : children) {
				this.parent.addComponent(child);
			}
		}
		return this.parent;
	}

	public Class<Component> getObjectType() {
		return Component.class;
	}

	public boolean isSingleton() {
		return true;
	}
}
package com.foo

import org.springframework.beans.factory.FactoryBean
import org.springframework.stereotype.Component

class ComponentFactoryBean : FactoryBean<Component> {

	private var parent: Component? = null
	private var children: List<Component>? = null

	fun setParent(parent: Component) {
		this.parent = parent
	}

	fun setChildren(children: List<Component>) {
		this.children = children
	}

	override fun getObject(): Component? {
		if (this.children != null && this.children!!.isNotEmpty()) {
			for (child in children!!) {
				this.parent!!.addComponent(child)
			}
		}
		return this.parent
	}

	override fun getObjectType(): Class<Component>? {
		return Component::class.java
	}

	override fun isSingleton(): Boolean {
		return true
	}
}

这工作得很好,但它将大量的Spring底层实现暴露给了最终用户。我们要做的是编写一个自定义扩展,将所有这些Spring底层实现隐藏起来。如果我们遵循之前描述的步骤,我们首先创建XSD模式来定义自定义标签的结构,如下例所示:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/component"
		xmlns:xsd="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.foo.example/schema/component"
		elementFormDefault="qualified"
		attributeFormDefault="unqualified">

	<xsd:element name="component">
		<xsd:complexType>
			<xsd:choice minOccurs="0" maxOccurs="unbounded">
				<xsd:element ref="component"/>
			</xsd:choice>
			<xsd:attribute name="id" type="xsd:ID"/>
			<xsd:attribute name="name" use="required" type="xsd:string"/>
		</xsd:complexType>
	</xsd:element>

</xsd:schema>

再次按照先前描述的过程, 我们然后创建一个自定义的NamespaceHandlerspring-doc.cadn.net.cn

package com.foo;

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

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

	public void init() {
		registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
	}
}
package com.foo

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

class ComponentNamespaceHandler : NamespaceHandlerSupport() {

	override fun init() {
		registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
	}
}

接下来是自定义的BeanDefinitionParser。请记住,我们正在创建一个BeanDefinition来描述一个ComponentFactoryBean。以下列出了我们的自定义BeanDefinitionParser实现:spring-doc.cadn.net.cn

package com.foo;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import java.util.List;

public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {

	protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
		return parseComponentElement(element);
	}

	private static AbstractBeanDefinition parseComponentElement(Element element) {
		BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
		factory.addPropertyValue("parent", parseComponent(element));

		List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
		if (childElements != null && childElements.size() > 0) {
			parseChildComponents(childElements, factory);
		}

		return factory.getBeanDefinition();
	}

	private static BeanDefinition parseComponent(Element element) {
		BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
		component.addPropertyValue("name", element.getAttribute("name"));
		return component.getBeanDefinition();
	}

	private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
		ManagedList<BeanDefinition> children = new ManagedList<>(childElements.size());
		for (Element element : childElements) {
			children.add(parseComponentElement(element));
		}
		factory.addPropertyValue("children", children);
	}
}
package com.foo

import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.support.ManagedList
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
import org.springframework.beans.factory.xml.ParserContext
import org.springframework.util.xml.DomUtils
import org.w3c.dom.Element

import java.util.List

class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() {

	override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? {
		return parseComponentElement(element)
	}

	private fun parseComponentElement(element: Element): AbstractBeanDefinition {
		val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java)
		factory.addPropertyValue("parent", parseComponent(element))

		val childElements = DomUtils.getChildElementsByTagName(element, "component")
		if (childElements != null && childElements.size > 0) {
			parseChildComponents(childElements, factory)
		}

		return factory.getBeanDefinition()
	}

	private fun parseComponent(element: Element): BeanDefinition {
		val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java)
		component.addPropertyValue("name", element.getAttribute("name"))
		return component.beanDefinition
	}

	private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) {
		val children = ManagedList<BeanDefinition>(childElements.size)
		for (element in childElements) {
			children.add(parseComponentElement(element))
		}
		factory.addPropertyValue("children", children)
	}
}

最后,需要将各种构件注册到Spring XML基础设施中, 通过修改META-INF/spring.handlersMETA-INF/spring.schemas文件,如下所示:spring-doc.cadn.net.cn

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd

“常规”元素上的自定义属性

编写自己的自定义解析器和相关工件并不难。然而,有时这样做并不一定是正确的选择。考虑一种情况,你需要向已有的 bean 定义添加元数据。在这种情况下,你肯定不想编写整个自定义扩展。相反,你只是想向现有的 bean 定义元素添加一个附加属性。spring-doc.cadn.net.cn

通过另一个示例,假设您为一个服务对象定义了一个bean定义,该服务对象(在不知情的情况下)访问了一个集群的JCache,并且您希望确保命名的JCache实例在周围的集群中被急切地启动。以下列表显示了这样的定义:spring-doc.cadn.net.cn

<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
		jcache:cache-name="checking.account">
	<!-- other dependencies here... -->
</bean>

我们可以在解析'jcache:cache-name'属性时创建另一个BeanDefinition。这个BeanDefinition然后为我们初始化命名的JCache。我们还可以修改BeanDefinition'checkingAccountService',使其依赖于这个新的初始化JCache的BeanDefinition。以下示例展示了我们的JCacheInitializerspring-doc.cadn.net.cn

package com.foo;

public class JCacheInitializer {

	private final String name;

	public JCacheInitializer(String name) {
		this.name = name;
	}

	public void initialize() {
		// lots of JCache API calls to initialize the named cache...
	}
}
package com.foo

class JCacheInitializer(private val name: String) {

	fun initialize() {
		// lots of JCache API calls to initialize the named cache...
	}
}

现在我们可以继续自定义扩展了。首先,我们需要编写描述自定义属性的XSD模式,如下所示:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/jcache"
		xmlns:xsd="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.foo.example/schema/jcache"
		elementFormDefault="qualified">

	<xsd:attribute name="cache-name" type="xsd:string"/>

</xsd:schema>

接下来,我们需要创建关联的NamespaceHandler,如下所示:spring-doc.cadn.net.cn

package com.foo;

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

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

	public void init() {
		super.registerBeanDefinitionDecoratorForAttribute("cache-name",
			new JCacheInitializingBeanDefinitionDecorator());
	}

}
package com.foo

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

class JCacheNamespaceHandler : NamespaceHandlerSupport() {

	override fun init() {
		super.registerBeanDefinitionDecoratorForAttribute("cache-name",
				JCacheInitializingBeanDefinitionDecorator())
	}

}

接下来,我们需要创建解析器。请注意,在这种情况下,因为我们将解析一个XML属性,所以我们写的是BeanDefinitionDecorator而不是BeanDefinitionParser。 以下代码段显示了我们的BeanDefinitionDecorator实现:spring-doc.cadn.net.cn

package com.foo;

import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {

	private static final String[] EMPTY_STRING_ARRAY = new String[0];

	public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
			ParserContext ctx) {
		String initializerBeanName = registerJCacheInitializer(source, ctx);
		createDependencyOnJCacheInitializer(holder, initializerBeanName);
		return holder;
	}

	private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
			String initializerBeanName) {
		AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
		String[] dependsOn = definition.getDependsOn();
		if (dependsOn == null) {
			dependsOn = new String[]{initializerBeanName};
		} else {
			List dependencies = new ArrayList(Arrays.asList(dependsOn));
			dependencies.add(initializerBeanName);
			dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
		}
		definition.setDependsOn(dependsOn);
	}

	private String registerJCacheInitializer(Node source, ParserContext ctx) {
		String cacheName = ((Attr) source).getValue();
		String beanName = cacheName + "-initializer";
		if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
			BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
			initializer.addConstructorArg(cacheName);
			ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
		}
		return beanName;
	}
}
package com.foo

import org.springframework.beans.factory.config.BeanDefinitionHolder
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.BeanDefinitionDecorator
import org.springframework.beans.factory.xml.ParserContext
import org.w3c.dom.Attr
import org.w3c.dom.Node

import java.util.ArrayList

class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator {

	override fun decorate(source: Node, holder: BeanDefinitionHolder,
						ctx: ParserContext): BeanDefinitionHolder {
		val initializerBeanName = registerJCacheInitializer(source, ctx)
		createDependencyOnJCacheInitializer(holder, initializerBeanName)
		return holder
	}

	private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder,
													initializerBeanName: String) {
		val definition = holder.beanDefinition as AbstractBeanDefinition
		var dependsOn = definition.dependsOn
		dependsOn = if (dependsOn == null) {
			arrayOf(initializerBeanName)
		} else {
			val dependencies = ArrayList(listOf(*dependsOn))
			dependencies.add(initializerBeanName)
			dependencies.toTypedArray()
		}
		definition.setDependsOn(*dependsOn)
	}

	private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String {
		val cacheName = (source as Attr).value
		val beanName = "$cacheName-initializer"
		if (!ctx.registry.containsBeanDefinition(beanName)) {
			val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java)
			initializer.addConstructorArg(cacheName)
			ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition())
		}
		return beanName
	}
}

最后,我们需要通过修改META-INF/spring.handlersMETA-INF/spring.schemas文件来将各种构件注册到Spring XML基础设施中,如下所示:spring-doc.cadn.net.cn

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd