XML スキーマオーサリング
バージョン 2.0 以降、Spring は、Bean を定義および構成するための基本的な Spring XML 形式にスキーマベースの拡張機能を追加するメカニズムを備えています。このセクションでは、独自のカスタム XML Bean 定義パーサーを作成し、そのようなパーサーを Spring IoC コンテナーに統合する方法について説明します。
スキーマ対応の XML エディターを使用する構成ファイルの作成を容易にするために、Spring の拡張可能な XML 構成メカニズムは XML スキーマに基づいています。標準の Spring ディストリビューションに付属する Spring の現在の XML 構成拡張機能に慣れていない場合は、最初に XML スキーマの前のセクションを読む必要があります。
新しい XML 構成拡張機能を作成するには:
統一された例として、型 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));
}
}
}
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
の作成は、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