XML Schema 编写
自 2.0 版本以来,Spring 提供了一种机制,可以向基本的 Spring XML 格式添加基于模式的扩展,以定义和配置 bean。本节将介绍如何编写自定义的 XML bean 定义解析器,并将这些解析器集成到 Spring IoC 容器中。
为了方便使用支持模式的 XML 编辑器编写配置文件,Spring 的可扩展 XML 配置机制基于 XML Schema。如果您不熟悉与标准 Spring 发行版一起提供的 Spring 当前 XML 配置扩展,您应该首先阅读上一节关于 XML Schemas 的内容。
要创建新的 XML 配置扩展:
为了统一示例,我们创建一个 XML 扩展(一个自定义 XML 元素),让我们能够配置 SimpleDateFormat
类型的对象(来自 java.text
包)。完成后,我们将能够如下定义 SimpleDateFormat
类型的 bean 定义:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
(我们在本附录后面包含了更详细的示例。这个简单示例的目的是引导您了解制作自定义扩展的基本步骤。)
编写模式
为 Spring 的 IoC 容器创建 XML 配置扩展的第一步是编写一个 XML 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>
指定的行包含所有可识别标签的扩展基础(意味着它们具有一个
id
属性,我们可以将其用作容器中的 bean 标识符)。我们可以使用这个属性,因为我们导入了 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>
前面两个代码片段中的第二个片段在容器中创建了一个 bean(由名称 dateFormat
标识,类型为 SimpleDateFormat
),并设置了一些属性。
基于模式的方法来创建配置格式允许与具有模式感知的 XML 编辑器的 IDE 紧密集成。通过使用正确编写的模式,您可以使用自动完成让用户在枚举中定义的多个配置选项之间进行选择。
编写一个 NamespaceHandler
除了模式,我们还需要一个 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 解析的繁重工作委托给 BeanDefinitionParsers
。这意味着每个 BeanDefinitionParser
仅包含解析单个自定义元素的逻辑,正如我们在下一步中看到的。
使用 BeanDefinitionParser
如果 NamespaceHandler
遇到已映射到特定 bean 定义解析器(在此情况下为 dateformat
)的 XML 元素,则使用 BeanDefinitionParser
。换句话说,BeanDefinitionParser
负责解析在模式中定义的一个独特的顶级 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) {
// 这将永远不会为 null,因为 schema 明确要求提供一个值
String pattern = element.getAttribute("pattern");
bean.addConstructorArgValue(pattern);
// 然而这是一个可选属性
String lenient = element.getAttribute("lenient");
if (StringUtils.hasText(lenient)) {
bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
}
}
}
我们使用 Spring 提供的
AbstractSingleBeanDefinitionParser
来处理创建单个BeanDefinition
的许多基本工作。我们向
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) {
// 这将永远不会为 null,因为 schema 明确要求提供一个值
val pattern = element.getAttribute("pattern")
bean.addConstructorArgValue(pattern)
// 然而这是一个可选属性
val lenient = element.getAttribute("lenient")
if (StringUtils.hasText(lenient)) {
bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
}
}
}
我们使用 Spring 提供的
AbstractSingleBeanDefinitionParser
来处理创建单个BeanDefinition
的许多基本工作。我们向
AbstractSingleBeanDefinitionParser
超类提供我们单个BeanDefinition
所代表的类型。
在这个简单的案例中,这就是我们需要做的全部。我们单个 BeanDefinition
的创建由 AbstractSingleBeanDefinitionParser
超类处理,正如 bean 定义的唯一标识符的提取和设置。
注册处理程序和模式
编码已经完成。剩下的工作就是让 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 文件中,通过 xsi:schemaLocation
属性引用该模式声明)。此文件是必需的,以防止 Spring 必须使用一个默认的 EntityResolver
,该解析器需要 Internet 访问来检索模式文件。如果您在此属性文件中指定映射,Spring 将在类路径中搜索模式(在本例中,即 org.springframework.samples.xml
包中的 myns.xsd
)。以下代码片段显示了我们需要为自定义模式添加的行:
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>
我们的自定义 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
属性公开一个设置方法。这使得通过使用设置器注入来配置 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
,该 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