XML Schema 編寫

從 2.0 版本開始,Spring 引入了一種機制,允許向用於定義和配置 Bean 的基本 Spring XML 格式新增基於 schema 的擴充套件。本節介紹如何編寫自己的自定義 XML Bean 定義解析器,並將這些解析器整合到 Spring IoC 容器中。

為了方便使用支援 schema 的 XML 編輯器編寫配置檔案,Spring 的可擴充套件 XML 配置機制基於 XML Schema。如果你不熟悉 Spring 標準發行版中自帶的當前 XML 配置擴充套件,應首先閱讀關於 XML Schema 的上一節。

建立新的 XML 配置擴充套件

  1. 編寫一個 XML schema 來描述你的自定義元素。

  2. 編寫一個自定義的 NamespaceHandler 實現。

  3. 編寫一個或多個 BeanDefinitionParser 實現(實際工作在這裡完成)。

  4. 註冊你的新工件到 Spring 中。

為了提供一個統一的示例,我們建立一個 XML 擴充套件(一個自定義 XML 元素),用於配置 SimpleDateFormat 型別的物件(來自 java.text 包)。完成後,我們將能夠按如下方式定義 SimpleDateFormat 型別的 Bean 定義:

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

(在本附錄後面會包含更詳細的示例。這個第一個簡單示例的目的是引導你瞭解建立自定義擴充套件的基本步驟。)

編寫 Schema

建立用於 Spring IoC 容器的 XML 配置擴充套件始於編寫一個 XML Schema 來描述該擴充套件。在我們的示例中,我們使用以下 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 名稱空間。

前面的 schema 允許我們使用 <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>

前面兩個片段中的第二個在容器中建立了一個 Bean(由名稱 dateFormat 標識,型別為 SimpleDateFormat),並設定了幾個屬性。

基於 schema 的配置格式建立方法可以與具有 schema 感知能力的 XML 編輯器 IDE 緊密整合。透過使用正確編寫的 schema,你可以利用自動補全功能,讓使用者在列舉中定義的多種配置選項之間進行選擇。

編寫 NamespaceHandler

除了 schema,我們還需要一個 NamespaceHandler 來解析 Spring 在解析配置檔案時遇到的所有屬於此特定名稱空間的元素。對於本示例,NamespaceHandler 應負責解析 myns:dateformat 元素。

NamespaceHandler 介面包含三個方法:

  • init(): 允許對 NamespaceHandler 進行初始化,在處理器使用前由 Spring 呼叫。

  • BeanDefinition parse(Element, ParserContext): 當 Spring 遇到頂層元素時(未巢狀在 Bean 定義或其他名稱空間中)呼叫。此方法可以自行註冊 Bean 定義、返回一個 Bean 定義,或兩者都做。

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext): 當 Spring 遇到不同名稱空間的屬性或巢狀元素時呼叫。一個或多個 Bean 定義的裝飾用於(例如)Spring 支援的作用域。我們首先重點介紹一個不使用裝飾的簡單示例,然後在一個更高階的示例中展示裝飾的使用。

雖然你可以為整個名稱空間編寫自己的 NamespaceHandler(從而提供解析名稱空間中每個元素的程式碼),但通常情況下,Spring XML 配置檔案中的每個頂層 XML 元素都會產生一個單獨的 Bean 定義(就像我們的例子,一個 <myns:dateformat/> 元素產生一個 SimpleDateFormat Bean 定義)。Spring 提供了一些便利類來支援這種情況。在以下示例中,我們使用 NamespaceHandlerSupport 類:

  • Java

  • Kotlin

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 可以處理其名稱空間中所有自定義元素的解析編排,同時委託給 BeanDefinitionParsers 來完成 XML 解析的繁重工作。這意味著每個 BeanDefinitionParser 只包含解析單個自定義元素的邏輯,正如我們在下一步中所看到的。

使用 BeanDefinitionParser

NamespaceHandler 遇到已對映到特定 Bean 定義解析器(本例中為 dateformat)型別的 XML 元素時,就會使用 BeanDefinitionParser。換句話說,BeanDefinitionParser 負責解析 schema 中定義的一個獨立的頂層 XML 元素。在解析器中,我們可以訪問該 XML 元素(及其子元素),以便解析我們的自定義 XML 內容,如下例所示:

  • Java

  • Kotlin

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 定義的唯一識別符號的提取和設定也由它處理。

註冊 Handler 和 Schema

編碼工作完成了。剩下的就是讓 Spring XML 解析基礎設施知道我們的自定義元素。我們透過在兩個特殊的目的屬性檔案中註冊我們的自定義 namespaceHandler 和自定義 XSD 檔案來做到這一點。這兩個屬性檔案都放在應用的 META-INF 目錄中,並且可以例如與你的二進位制類一起打包在 JAR 檔案中分發。Spring XML 解析基礎設施透過讀取這些特殊屬性檔案來自動識別你的新擴充套件,這些檔案的格式在接下來兩節中詳細介紹。

編寫 META-INF/spring.handlers

名為 spring.handlers 的屬性檔案包含 XML Schema URI 到名稱空間處理類(namespace handler classes)的對映。對於我們的示例,我們需要編寫以下內容:

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

: 字元在 Java properties 格式中是有效的分隔符,因此 URI 中的 : 字元需要用反斜槓轉義。)

鍵值對的第一部分(鍵)是與你的自定義名稱空間擴充套件關聯的 URI,並且需要與自定義 XSD schema 中指定的 targetNamespace 屬性值完全一致。

編寫 'META-INF/spring.schemas'

名為 spring.schemas 的屬性檔案包含 XML Schema 位置(在使用 schema 的 XML 檔案中,作為 xsi:schemaLocation 屬性的一部分引用,連同 schema 宣告)到類路徑資源的對映。需要此檔案是為了防止 Spring 絕對必須使用需要網際網路訪問才能檢索 schema 檔案的預設 EntityResolver。如果你在此屬性檔案中指定對映,Spring 會在類路徑中搜索 schema(本例中是 org.springframework.samples.xml 包中的 myns.xsd)。以下片段顯示了我們需要為自定義 schema 新增的行:

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

(記住 : 字元必須轉義。)

建議將你的 XSD 檔案(或多個檔案)與 NamespaceHandlerBeanDefinitionParser 類一起部署在類路徑上。

在 Spring XML 配置中使用自定義擴充套件

使用你自己實現的自定義擴充套件與使用 Spring 提供的“自定義”擴充套件沒有區別。以下示例在 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 我們的自定義 bean。

更多詳細示例

本節介紹了一些更詳細的自定義 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: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

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 方法。以下列表顯示了這樣一個自定義 FactoryBean

  • Java

  • Kotlin

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 schema 來定義我們的自定義標籤的結構,如下列表所示:

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

再次遵循之前描述的過程,然後我們建立一個自定義的 NamespaceHandler

  • Java

  • Kotlin

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。記住,我們正在建立一個描述 ComponentFactoryBeanBeanDefinition。以下列表顯示了我們的自定義 BeanDefinitionParser 實現:

  • Java

  • Kotlin

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

# 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 定義元素新增一個附加屬性。

再舉一個例子,假設你為一個服務物件定義了一個 bean 定義,該服務物件(它自己不知道)訪問一個叢集的 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

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

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

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 實現

  • Java

  • Kotlin

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 基礎設施中,如下所示

# 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