XML Schema Authoring

  • XSD 架构:描述自定义元素的结构和属性。

  • NamespaceHandler:处理自定义命名空间中的 XML 元素,并委托给 BeanDefinitionParsers。

  • BeanDefinitionParser:解析单个自定义 XML 元素并创建一个相应的 BeanDefinition。

使用这些组件,可以实现各种扩展场景,例如:

  • 创建嵌套的自定义元素。

  • 在普通元素上添加自定义属性。

自 2.0 版本以来,Spring 提供了一种机制,可向用于定义和配置 bean 的基本 Spring XML 格式添加基于架构的扩展。本节介绍如何编写你自己的自定义 XML bean 定义解析器,并将此类解析器集成到 Spring IoC 容器中。 为了便于编写使用感知模式的 XML 编辑器的配置文件,Spring 可扩展的 XML 配置机制基于 XML 模式。如果你不熟悉标配 Spring 发行版附带的 Spring 当前 XML 配置扩展,则应首先阅读 XML Schemas 一节中有关其的介绍。 如要创建新的 XML 配置扩展:

  1. Author 一个 XML 模式来描述你的自定义元素。

  2. Code 一个自定义 NamespaceHandler 实施。

  3. Code 一个或多个 BeanDefinitionParser 实施(这是真正的工作所在)。

  4. Register 使用 Spring 将你的新产品组装起来。

对于一个统一示例,我们创建一个 XML 扩展(一个自定义 XML 元素),它让我们能够配置 SimpleDateFormat(来自 java.text 包)类型的对象。完成后,我们将能够按如下方式定义 SimpleDateFormat 类型的 bean 定义:

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

(我们稍后将在本附录中包含更加详细的示例。此第一个简单示例的目的是带你了解创建自定义扩展的基本步骤。)

Authoring the Schema

创建用于与 Spring 的 IoC 容器一起使用的 XML 配置扩展首先从创作一个 XML Schema 以描述扩展开始。对于我们的示例,我们使用以下架构来配置 SimpleDateFormat 对象:

<!-- 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 名称空间。

前面的架构让我们能够通过使用 myns:dateformat 元素直接在一个 XML 应用程序上下文文件配置 SimpleDateFormat 对象,如下面的示例所示:

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

请注意,在我们创建了基础设施类之后,前面的 XML 代码片段本质上与以下 XML 代码片段相同:

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

前面两个片段的第二个片段会在容器中创建(由简称为`SimpleDateFormat` 的名称 dateFormat 识别)一个 bean,其中设置了一些属性。

用于创建配置格式的基于架构的方法支持与具有识别架构的 XML 编辑器的 IDE 的紧密集成。通过使用经适当地创作的架构,您可以使用自动完成功能使用户在枚举中定义的不同配置选项之间进行选择。

Coding a NamespaceHandler

除了架构之外,我们需要一个 NamespaceHandler 来解析 Spring 在解析配置文件时遇到的此特定命名空间的所有元素。对于此示例,NamespaceHandler 应当负责解析 myns:dateformat 元素。

NamespaceHandler 接口具有三种方法:

  • init():允许初始化 NamespaceHandler ,Spring 在使用处理程序之前会调用它。

  • BeanDefinition parse(Element, ParserContext):在 Spring 遇到一个顶级元素时调用(未嵌套在 Bean 定义或其他名称空间中)。该方法本身可以注册 Bean 定义,返回 Bean 定义,或同时进行。

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext):在 Spring 遇到一个不同名称空间的属性或嵌套元素时调用。一个或多个 Bean 定义的修饰被用来(例如使用) scopes that Spring supports 。我们先突出一个不使用修饰的简单示例,然后在稍高级的示例中展示修饰。

虽然你可以为整个命名空间编写自己的 NamespaceHandler(因而提供解析该命名空间中的每个元素的代码),但经常会出现 Spring XML 配置文件中的每个顶层 XML 元素而导致一个 bean 定义(例如我们的 myns:dateformat 元素导致了一个 SimpleDateFormat bean 定义)。Spring 具有支持此方案的许多便捷类。在以下示例中,我们使用 NamespaceHandlerSupport 类:

  • Java

  • Kotlin

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

public class MyNamespaceHandler extends NamespaceHandlerSupport {

	public void init() {
		registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
	}
}
import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class MyNamespaceHandler : NamespaceHandlerSupport {

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

你可能会注意到此类中实际上没有许多解析逻辑。事实上,NamespaceHandlerSupport 类具有一个内置的委托概念。它支持注册任意数量的 BeanDefinitionParser 实例,当它需要解析其命名空间中的一个元素时,它会委托给此类实例。此种清晰的分离方案让 NamespaceHandler 处理其命名空间中所有自定义元素的解析编排,同时委托给`BeanDefinitionParsers` 来执行 XML 解析的基本工作。这意味着每个 BeanDefinitionParser 仅包含用于解析单个自定义元素的逻辑,如我们在下一步中看到的。

Using BeanDefinitionParser

如果 NamespaceHandler 遇到了已被映射到特定 bean 定义解析器(本例中为 dateformat)的类型的 XML 元素,则会用到 BeanDefinitionParser。换句话说,BeanDefinitionParser 负责解析架构中定义的某个不同的顶层 XML 元素。在解析器中,我们可以访问 XML 元素(因而也可以访问其子元素),以便能够解析我们的自定义 XML 内容,如你在以下示例中看到的:

Java
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 我们使用我们的单个 BeanDefinition 表示的类型为 AbstractSingleBeanDefinitionParser 超类提供类型。
Kotlin
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))
		}
	}
}
3 我们使用 Spring 提供的 AbstractSingleBeanDefinitionParser 来处理创建单个 BeanDefinition 的大量基本繁重工作。
4 我们使用我们的单个 BeanDefinition 表示的类型为 AbstractSingleBeanDefinitionParser 超类提供类型。

在此简单示例中,这是我们唯一需要执行的操作。创建我们的单个`BeanDefinition` 由父类 AbstractSingleBeanDefinitionParser 处理,bean 定义的唯一标识符的提取和设置也是如此。

Registering the Handler and the Schema

编码已完成。剩余的就是让 Spring XML 解析基础设施感知我们的自定义元素。我们通过在两个特殊用途属性文件中注册我们的自定义 namespaceHandler 和自定义 XSD 文件来执行此操作。这些属性文件都放置在你的应用程序中的 META-INF 目录中,并且可以例如随你 JAR 文件中的二进制类一起分发。Spring XML 解析基础设施通过使用这些特殊属性文件(它们的格式在下两节中详细介绍)自动选择你的新扩展。

Writing META-INF/spring.handlers

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

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

: 字符是 Java 属性格式中的一个有效分隔符,因此 URI 中的`:` 字符需要使用反斜线转义。)

键值对中的第一部分(键)是你自定义命名空间扩展关联的 URI,它需要与在你的自定义 XSD 架构中指定的 targetNamespace 属性值完全匹配。

Writing 'META-INF/spring.schemas'

名为 spring.schemas 的属性文件包含了 XML 模式位置(在使用该模式作为 xsi:schemaLocation 属性一部分的 XML 中,将模式声明与该模式一起引用)到类路径资源的映射。需要这个文件来防止 Spring 绝对必须使用需要互联网访问才能检索模式文件的默认 EntityResolver。如果你在此属性文件中指定了映射,Spring 就会在类路径上搜索该模式(在本例中为 org.springframework.samples.xml 包中的 myns.xsd)。以下代码段显示了我们为我们的自定义模式需要添加的行:

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

(请记住,必须转义 : 字符。)

我们建议你将你的 XSD 文件(或文件)与类路径上的 NamespaceHandlerBeanDefinitionParser 类一起部署。

Using a Custom Extension in Your Spring XML Configuration

使用你自己实现的自定义扩展与使用 Spring 提供的“custom”扩展之一没有区别。以下示例在 Spring XML 配置文件中使用了前面步骤中开发的自定义 <dateformat/> 元素:

<?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 Our custom bean.

More Detailed Examples

本节介绍了一些自定义 XML 扩展的更详细的示例。

Nesting Custom Elements within Custom Elements

本节中给出的示例说明了如何编写要满足以下配置目标所需的各种神器:

<?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 类:

  • Java

  • Kotlin

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;
	}
}
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
	}
}

此问题的典型解决方案是创建一个公开 components 属性的 setter 属性的自定义 FactoryBean。以下清单展示了这样的自定义 FactoryBean

  • Java

  • Kotlin

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;
	}
}
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 管道隐藏起来。如果我们坚持使用 the steps described previously,那么我们首先要创建 XSD 模式,以此来定义自定义标记的结构,如下面清单所示:

<?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>

the process described earlier一样,然后我们创建一个自定义 NamespaceHandler

  • Java

  • Kotlin

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

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

	public void init() {
		registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
	}
}
import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class ComponentNamespaceHandler : NamespaceHandlerSupport() {

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

接下来是自定义 BeanDefinitionParser。请记住,我们正在创建一个描述 ComponentFactoryBeanBeanDefinition。以下清单显示了我们的自定义 BeanDefinitionParser 实现:

  • Java

  • Kotlin

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);
	}
}
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 基础设施注册各种神器,如下所示:

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

Custom Attributes on “Normal” Elements

编写自己的自定义解析器和相关神器并不难。但是,有时这样做并不是正确的选择。考虑一种场景,你需要向已存在的 Bean 定义添加元数据。在这种情况下,你肯定不想编写自己的整个自定义扩展名。相反,你只需要向现有的 Bean 定义元素添加一个附加属性。

通过另一个示例,假设你为服务对象定义了一个 Bean 定义,该对象(自己不知道)访问一个集群 https://www.jcp.org/en/jsr/detail?id=107 [JCache],并且你希望确保命名的 JCache 实例在周围集群中急切启动。以下清单显示了这样的定义:

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

然后,当解析 'jcache:cache-name' 属性时,我们可以再创建一个 BeanDefinition。然后,此 BeanDefinition 为我们初始化命名的 JCache。我们还可以修改 'checkingAccountService' 的现有的 BeanDefinition,以便它依赖于此新的 JCache 初始化 BeanDefinition。以下清单显示了我们的 JCacheInitializer

  • Java

  • Kotlin

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...
	}
}
class JCacheInitializer(private val name: String) {

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

现在,我们可以转到自定义扩展名了。首先,我们需要编写描述自定义属性的 XSD 架构,如下所示:

<?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,如下所示:

  • Java

  • Kotlin

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

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

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

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

class JCacheNamespaceHandler : NamespaceHandlerSupport() {

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

}

接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析 XML 属性,所以我们编写 BeanDefinitionDecorator 而不是 BeanDefinitionParser。以下清单显示了我们的 BeanDefinitionDecorator 实现:

  • Java

  • Kotlin

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;
	}
}
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 基础设施,如下所示:

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