XML スキーマオーサリング

バージョン 2.0 以降、Spring は、Bean を定義および構成するための基本的な Spring XML 形式にスキーマベースの拡張機能を追加するメカニズムを備えています。このセクションでは、独自のカスタム XML Bean 定義パーサーを作成し、そのようなパーサーを Spring IoC コンテナーに統合する方法について説明します。

スキーマ対応の XML エディターを使用する構成ファイルの作成を容易にするために、Spring の拡張可能な XML 構成メカニズムは XML スキーマに基づいています。標準の Spring ディストリビューションに付属する Spring の現在の XML 構成拡張機能に慣れていない場合は、最初に XML スキーマの前のセクションを読む必要があります。

新しい XML 構成拡張機能を作成するには:

  1. 作成者カスタム要素を記述する XML スキーマ。

  2. コードカスタム NamespaceHandler 実装。

  3. コード 1 つ以上の BeanDefinitionParser 実装(これは実際の作業が行われる場所です)。

  4. Spring を使用して、新しい成果物を登録

統一された例として、型 SimpleDateFormat のオブジェクト(java.text パッケージから)を構成できる XML 拡張機能(カスタム XML 要素)を作成します。完了したら、次のように型 SimpleDateFormat の Bean 定義を定義できます。

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

(この付録の後の方で、さらに詳細な例を示します。この最初の簡単な例の目的は、カスタム拡張機能を作成する基本的な手順を説明することです)

スキーマの作成

Spring の IoC コンテナーで使用する XML 構成拡張機能の作成は、拡張機能を記述する XML スキーマの作成から始まります。この例では、次のスキーマを使用して 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 示された行には、識別可能なすべてのタグの拡張ベースが含まれています(コンテナー内で Bean 識別子として使用できる id 属性があることを意味します)。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>

上記の 2 つのスニペットの 2 番目は、コンテナーに Bean(型 SimpleDateFormat の名前 dateFormat で識別される)を作成し、いくつかのプロパティを設定します。

構成フォーマットを作成するためのスキーマベースのアプローチにより、スキーマ対応の XML エディターを備えた IDE との緊密な統合が可能になります。適切に作成されたスキーマを使用することにより、自動補完を使用して、ユーザーが列挙で定義されたいくつかの構成オプションから選択できるようにすることができます。

NamespaceHandler のコーディング

スキーマに加えて、Spring が構成ファイルの解析中に遭遇するこの特定の名前空間のすべての要素を解析するために、NamespaceHandler が必要です。この例では、NamespaceHandler は myns:dateformat 要素の解析を処理する必要があります。

NamespaceHandler インターフェースには 3 つの方法があります。

  • init()NamespaceHandler の初期化を許可し、ハンドラーが使用される前に Spring によって呼び出されます。

  • BeanDefinition parse(Element, ParserContext): Spring が最上位要素(Bean 定義または別のネームスペース内にネストされていない)に遭遇したときに呼び出されます。このメソッド自体は、Bean 定義を登録するか、Bean 定義を返すか、その両方を行うことができます。

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext): Spring が別の名前空間の属性またはネストされた要素に遭遇したときに呼び出されます。1 つ以上の 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 インスタンスの登録をサポートします。BeanDefinitionParser インスタンスは、ネームスペースの要素を解析する必要があるときに委譲されます。このように関心事を明確に分離することにより、NamespaceHandler は、ネームスペース内のすべてのカスタム要素の解析のオーケストレーションを処理しながら、BeanDefinitionParsers に委譲して XML 解析の面倒な作業を行うことができます。これは、次のステップでわかるように、各 BeanDefinitionParser には単一のカスタム要素を解析するためのロジックのみが含まれることを意味します。

BeanDefinitionParser を使用する

NamespaceHandler が特定の Bean 定義パーサー(この場合は dateformat)にマップされた型の XML エレメントを検出すると、BeanDefinitionParser が使用されます。言い換えれば、BeanDefinitionParser は、スキーマで定義された 1 つの異なる最上位 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));
		}
	}

}
1Spring が提供する AbstractSingleBeanDefinitionParser を使用して、単一の BeanDefinition を作成する多くの基本的な単調な作業を処理します。
2AbstractSingleBeanDefinitionParser スーパークラスには、単一の 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))
		}
	}
}
1Spring が提供する AbstractSingleBeanDefinitionParser を使用して、単一の BeanDefinition を作成する多くの基本的な単調な作業を処理します。
2AbstractSingleBeanDefinitionParser スーパークラスには、単一の BeanDefinition が表す型を提供します。

この単純なケースでは、これが必要なすべてです。単一の BeanDefinition の作成は、Bean 定義の一意の識別子の抽出および設定と同様に、AbstractSingleBeanDefinitionParser スーパークラスによって処理されます。

ハンドラーとスキーマの登録

コーディングが終了しました。あとは、Spring XML 解析インフラストラクチャにカスタム要素を認識させるだけです。これを行うには、カスタム namespaceHandler とカスタム XSD ファイルを 2 つの特別な目的のプロパティファイルに登録します。これらのプロパティファイルは両方とも、アプリケーションの META-INF ディレクトリに配置され、たとえば、JAR ファイルのバイナリクラスと一緒に配布できます。Spring XML 解析インフラストラクチャは、これらの特別なプロパティファイルを使用して新しい拡張機能を自動的に選択します。その形式については、次の 2 つのセクションで詳しく説明します。

META-INF/spring.handlers の作成

spring.handlers というプロパティファイルには、名前空間ハンドラークラスへの XML スキーマ URI のマッピングが含まれています。この例では、次を記述する必要があります。

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

: 文字は Java プロパティ形式の有効な区切り文字であるため、URI の : 文字はバックスラッシュでエスケープする必要があります)

キーと値のペアの最初の部分(キー)は、カスタム名前空間拡張に関連付けられた URI であり、カスタム XSD スキーマで指定されている targetNamespace 属性の値と正確に一致する必要があります。

"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

: 文字はエスケープする必要があることに注意してください)

クラスパス上の NamespaceHandler クラスと BeanDefinitionParser クラスのすぐ横に XSD ファイルをデプロイすることをお勧めします。

Spring XML 構成でのカスタム拡張機能の使用

自分で実装したカスタム拡張機能を使用することは、Spring が提供する「カスタム」拡張機能の 1 つを使用することと同じです。次の例では、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
	}
}

この課題の一般的な解決策は、components プロパティの setter プロパティを公開するカスタム FactoryBean を作成することです。次のリストは、そのようなカスタム 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 スキーマを作成することから始めます。

<?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 です。ComponentFactoryBean を記述する BeanDefinition を作成していることに注意してください。次のリストは、カスタム 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.handlers および META-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

「通常の」要素のカスタム属性

独自のカスタムパーサーと関連するアーティファクトを作成するのは難しくありません。ただし、それが正しいことではない場合もあります。既存の Bean 定義にメタデータを追加する必要があるシナリオを考えてみましょう。この場合、独自のカスタム拡張機能全体を作成する必要はありません。むしろ、既存の Bean 定義要素に属性を追加するだけです。

別の例として、クラスター化された JCache (英語) にアクセスする(不明な)サービスオブジェクトの Bean 定義を定義し、指定された 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 属性を解析するため、BeanDefinitionParser ではなく BeanDefinitionDecorator を記述することに注意してください。以下のリストは、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.handlers および META-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