XML Schema 撰寫

自 2.0 版本以來,Spring 具有一種機制,可將基於 Schema 的擴充功能新增至基本的 Spring XML 格式,以定義和組態 Bean。本節介紹如何編寫您自己的自訂 XML Bean 定義解析器,以及如何將這些解析器整合到 Spring IoC 容器中。

為了方便撰寫使用 Schema 感知 XML 編輯器的組態檔,Spring 的可擴充 XML 組態機制是以 XML Schema 為基礎。如果您不熟悉標準 Spring 發行版中隨附的 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 namespace。

先前的 Schema 讓我們可以直接在 XML 應用程式 Context 檔案中使用 <myns:dateformat/> 元素來組態 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 在解析組態檔時遇到的此特定 namespace 的所有元素。在此範例中,NamespaceHandler 應負責解析 myns:dateformat 元素。

NamespaceHandler 介面具有三種方法

  • init():允許初始化 NamespaceHandler,並在處理程序使用前由 Spring 呼叫。

  • BeanDefinition parse(Element, ParserContext):當 Spring 遇到頂層元素時呼叫 (未巢狀於 Bean 定義或不同的 namespace 內)。此方法本身可以註冊 Bean 定義、傳回 Bean 定義,或兩者兼具。

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext):當 Spring 遇到不同 namespace 的屬性或巢狀元素時呼叫。一個或多個 Bean 定義的裝飾用於 (例如) Spring 支援的作用域。我們先強調一個簡單的範例,不使用裝飾,然後在稍後更進階的範例中展示裝飾。

雖然您可以為整個 namespace 編碼自己的 NamespaceHandler (因此提供程式碼來解析 namespace 中的每個元素),但通常情況是,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 實例,當需要解析其 namespace 中的元素時,它會委派給這些實例。這種對關注點的清晰分離讓 NamespaceHandler 可以處理解析其 namespace 中所有自訂元素的協調,同時委派給 BeanDefinitionParser 來完成 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 定義的唯一識別符的提取和設定也是如此。

註冊處理程序和 Schema

編碼已完成。剩下要做的就是讓 Spring XML 解析基礎架構知道我們的自訂元素。我們透過在兩個特殊用途的屬性檔中註冊我們的自訂 namespaceHandler 和自訂 XSD 檔來完成此操作。這些屬性檔都放置在應用程式的 META-INF 目錄中,並且可以例如與 JAR 檔中的二進制類別一起分發。Spring XML 解析基礎架構透過使用這些特殊屬性檔自動擷取您的新擴充功能,這些屬性檔的格式在接下來的兩節中詳細說明。

撰寫 META-INF/spring.handlers

名為 spring.handlers 的屬性檔包含 XML Schema URI 到 namespace 處理程序類別的對應。對於我們的範例,我們需要撰寫以下內容

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

(: 字元是 Java 屬性格式中的有效分隔符號,因此 URI 中的 : 字元需要使用反斜線逸出。)

鍵值對的第一部分 (鍵) 是與您的自訂 namespace 擴充功能相關聯的 URI,並且需要完全符合您的自訂 XSD Schema 中指定的 targetNamespace 屬性的值。

撰寫 'META-INF/spring.schemas'

名為 spring.schemas 的屬性檔包含 XML Schema 位置 (在 XML 檔中與 Schema 宣告一起參考,這些 XML 檔使用 Schema 作為 xsi:schemaLocation 屬性的一部分) 到類別路徑資源的對應。需要此檔案以防止 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)
	}
}

最後,需要透過修改 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

「一般」元素上的自訂屬性

撰寫您自己的自訂解析器和相關組件並不困難。但是,有時這並不是正確的做法。考慮一個您需要將 Metadata 新增到已存在的 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 結構描述,如下所示

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