XML 模式创作
从 2.0 版本开始,Spring 就提供了一种机制,用于向基本的 Spring XML 格式添加基于模式的扩展,以定义和配置 Bean。本节介绍如何编写自己的自定义 XML Bean 定义解析器,并将这些解析器集成到 Spring IoC 容器中。
为了便于编写使用模式感知 XML 编辑器的配置文件,Spring 的可扩展 XML 配置机制基于 XML 模式。如果您不熟悉 Spring 标准发行版附带的当前 XML 配置扩展,则应首先阅读上一节关于 XML 模式 的内容。
要创建新的 XML 配置扩展
为了统一示例,我们创建一个 XML 扩展(一个自定义的 XML 元素),它允许我们配置 SimpleDateFormat
类型(来自 java.text
包)的对象。完成后,我们将能够定义以下类型的 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 | 指示的行包含所有可识别标签的扩展基础(意味着它们具有一个 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(由 SimpleDateFormat
类型的 dateFormat
名称标识),并设置了几个属性。
基于模式的创建配置格式的方法允许与具有模式感知 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
能够处理其命名空间中所有自定义元素的解析的编排,同时委托给BeanDefinitionParsers
来完成 XML 解析的繁重工作。这意味着每个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) {
// 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 定义的唯一标识符的提取和设置也是如此。
注册处理器和模式
编码已完成。剩下的就是让 Spring XML 解析基础设施了解我们的自定义元素。我们通过在两个特殊用途的属性文件中注册自定义的 namespaceHandler
和自定义 XSD 文件来实现。这两个属性文件都放置在应用程序的 META-INF
目录中,例如,可以与您的二进制类一起打包在 JAR 文件中。Spring XML 解析基础设施会自动通过使用这些特殊的属性文件来获取您的新扩展,其格式将在接下来的两节中详细介绍。
编写 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 绝对必须使用需要 Internet 访问权限来检索模式文件的默认 EntityResolver
。如果您在此属性文件中指定映射,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>
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