XML Schema 编写
从 2.0 版本开始,Spring 引入了一种机制,允许向用于定义和配置 Bean 的基本 Spring XML 格式添加基于 schema 的扩展。本节介绍如何编写自己的自定义 XML Bean 定义解析器,并将这些解析器集成到 Spring IoC 容器中。
为了方便使用支持 schema 的 XML 编辑器编写配置文件,Spring 的可扩展 XML 配置机制基于 XML Schema。如果你不熟悉 Spring 标准发行版中自带的当前 XML 配置扩展,应首先阅读关于 XML Schema 的上一节。
创建新的 XML 配置扩展
为了提供一个统一的示例,我们创建一个 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
),并设置了几个属性。
基于 schema 的配置格式创建方法可以与具有 schema 感知能力的 XML 编辑器 IDE 紧密集成。通过使用正确编写的 schema,你可以利用自动补全功能,让用户在枚举中定义的多种配置选项之间进行选择。 |
编写 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
可以处理其命名空间中所有自定义元素的解析编排,同时委托给 BeanDefinitionParsers
来完成 XML 解析的繁重工作。这意味着每个 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 定义的唯一标识符的提取和设置也由它处理。
注册 Handler 和 Schema
编码工作完成了。剩下的就是让 Spring XML 解析基础设施知道我们的自定义元素。我们通过在两个特殊的目的属性文件中注册我们的自定义 namespaceHandler
和自定义 XSD 文件来做到这一点。这两个属性文件都放在应用的 META-INF
目录中,并且可以例如与你的二进制类一起打包在 JAR 文件中分发。Spring XML 解析基础设施通过读取这些特殊属性文件来自动识别你的新扩展,这些文件的格式在接下来两节中详细介绍。
编写 META-INF/spring.handlers
名为 spring.handlers
的属性文件包含 XML Schema URI 到命名空间处理类(namespace handler classes)的映射。对于我们的示例,我们需要编写以下内容:
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(:
字符在 Java properties 格式中是有效的分隔符,因此 URI 中的 :
字符需要用反斜杠转义。)
键值对的第一部分(键)是与你的自定义命名空间扩展关联的 URI,并且需要与自定义 XSD schema 中指定的 targetNamespace
属性值完全一致。
编写 'META-INF/spring.schemas'
名为 spring.schemas
的属性文件包含 XML Schema 位置(在使用 schema 的 XML 文件中,作为 xsi:schemaLocation
属性的一部分引用,连同 schema 声明)到类路径资源的映射。需要此文件是为了防止 Spring 绝对必须使用需要互联网访问才能检索 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 schema 来定义我们的自定义标签的结构,如下列表所示:
<?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)
}
}
最后,需要将各种工件注册到 Spring XML 基础设施中,方法是修改 META-INF/spring.handlers
和 META-INF/spring.schemas
文件,如下所示:
# 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 schema,如下所示
<?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