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

XML Schema 编写

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

为了便于使用支持 XML Schema 的 XML 编辑器编写配置文件, Spring 的可扩展 XML 配置机制基于 XML Schema 构建。如果您还不熟悉 Spring 标准发行版中自带的当前 XML 配置扩展, 请先阅读前面关于 XML Schemas 的章节。spring-doc.cadn.net.cn

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

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

  2. 代码 一个自定义的 NamespaceHandler 实现。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 来描述该扩展。在我们的示例中,使用以下 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 命名空间。

上述模式允许我们通过使用 SimpleDateFormat 元素,直接在 XML 应用上下文文件中配置 <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

基于 Schema 的配置格式创建方法可与支持 Schema 感知的 XML 编辑器的 IDE 紧密集成。通过使用编写得当的 Schema,您可以利用自动补全功能,让用户从枚举中定义的多个配置选项中进行选择。

编码一个NamespaceHandler

除了该 schema 之外,我们还需要一个 NamespaceHandler,用于解析 Spring 在解析配置文件时遇到的此特定命名空间下的所有元素。在本例中,NamespaceHandler 应负责解析 myns:dateformat 元素。spring-doc.cadn.net.cn

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 定义的装饰(decoration)被用于(例如)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 遇到已被映射到特定 Bean 定义解析器的 XML 元素类型时(本例中为 NamespaceHandler),就会使用 dateformat。换句话说,BeanDefinitionParser 负责解析 schema 中定义的一个特定的顶层 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,必须与您自定义 XSD 架构中指定的 targetNamespace 属性值完全一致。spring-doc.cadn.net.cn

正在写入 'META-INF/spring.schemas'

名为 spring.schemas 的属性文件包含 XML Schema 位置(在使用该 Schema 的 XML 文件中,与 Schema 声明一起作为 xsi:schemaLocation 属性的一部分被引用)到类路径资源的映射。此文件的作用是避免 Spring 必须使用默认的 EntityResolver,而该默认解析器需要通过互联网访问来获取 Schema 文件。如果你在此属性文件中指定了映射关系,Spring 就会在类路径中查找该 Schema(在本例中,即 myns.xsd 包中的 org.springframework.samples.xml)。以下代码片段展示了我们需要为自定义 Schema 添加的配置行: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,为其 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。请记住,我们正在创建一个描述 BeanDefinitionComponentFactoryBean。以下代码清单展示了我们自定义的 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)
	}
}

最后,需要通过修改 META-INF/spring.handlersMETA-INF/spring.schemas 文件,将各种构件注册到 Spring XML 基础设施中,如下所示: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>

然后,当解析 BeanDefinition 属性时,我们可以创建另一个 'jcache:cache-name'。该 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