XML Schema 創作
自版本 2.0 以來,Spring 提供了一種機制,可以向用於定義和配置 bean 的基本 Spring XML 格式新增基於模式的擴充套件。本節介紹如何編寫自己的自定義 XML bean 定義解析器,以及如何將這些解析器整合到 Spring IoC 容器中。
為了方便創作使用支援模式的 XML 編輯器的配置檔案,Spring 的可擴充套件 XML 配置機制基於 XML Schema。如果你不熟悉 Spring 標準發行版中附帶的當前 XML 配置擴充套件,你應該首先閱讀前一節關於XML Schema的內容。
建立新的 XML 配置擴充套件
-
編寫 XML Schema 來描述你的自定義元素。
-
編寫自定義
NamespaceHandler實現。 -
編寫一個或多個
BeanDefinitionParser實現(這是實際工作完成的地方)。 -
向 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),並設定了幾個屬性。
| 基於模式的配置格式建立方法允許與具有模式感知 XML 編輯器的 IDE 緊密整合。透過使用編寫正確的模式,你可以使用自動完成功能讓使用者在列舉中定義的多個配置選項之間進行選擇。 |
編寫 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 能夠處理其名稱空間中所有自定義元素的解析編排,同時將 XML 解析的繁重工作委託給 BeanDefinitionParser。這意味著每個 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 到名稱空間處理器類的對映。對於我們的示例,我們需要編寫以下內容:
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(: 字元是 Java 屬性格式中有效的定界符,因此 URI 中的 : 字元需要用反斜槓轉義。)
鍵值對的第一部分(鍵)是與你的自定義名稱空間擴充套件關聯的 URI,並且需要與你的自定義 XSD 模式中指定的 targetNamespace 屬性的值完全匹配。
編寫 'META-INF/spring.schemas'
名為 spring.schemas 的屬性檔案包含 XML Schema 位置(在 XML 檔案中與 schema 宣告一起引用,這些檔案使用 schema 作為 xsi:schemaLocation 屬性的一部分)到類路徑資源的對映。此檔案用於防止 Spring 絕對必須使用需要 Internet 訪問才能檢索 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 檔案與 NamespaceHandler 和 BeanDefinitionParser 類一起部署到類路徑上。
在 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 模式來定義自定義標籤的結構,如以下列表所示:
<?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 定義元素新增一個附加屬性。
舉一個例子,假設你為一個服務物件定義了一個 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.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