XML Schema Authoring
-
XSD 架构:描述自定义元素的结构和属性。
-
NamespaceHandler:处理自定义命名空间中的 XML 元素,并委托给 BeanDefinitionParsers。
-
BeanDefinitionParser:解析单个自定义 XML 元素并创建一个相应的 BeanDefinition。
使用这些组件,可以实现各种扩展场景,例如:
-
创建嵌套的自定义元素。
-
在普通元素上添加自定义属性。
自 2.0 版本以来,Spring 提供了一种机制,可向用于定义和配置 bean 的基本 Spring XML 格式添加基于架构的扩展。本节介绍如何编写你自己的自定义 XML bean 定义解析器,并将此类解析器集成到 Spring IoC 容器中。
Since version 2.0, Spring has featured a mechanism for adding schema-based extensions to the basic Spring XML format for defining and configuring beans. This section covers how to write your own custom XML bean definition parsers and integrate such parsers into the Spring IoC container.
为了便于编写使用感知模式的 XML 编辑器的配置文件,Spring 可扩展的 XML 配置机制基于 XML 模式。如果你不熟悉标配 Spring 发行版附带的 Spring 当前 XML 配置扩展,则应首先阅读 XML Schemas 一节中有关其的介绍。
To facilitate authoring configuration files that use a schema-aware XML editor, Spring’s extensible XML configuration mechanism is based on XML Schema. If you are not familiar with Spring’s current XML configuration extensions that come with the standard Spring distribution, you should first read the previous section on XML Schemas.
如要创建新的 XML 配置扩展:
To create new XML configuration extensions:
对于一个统一示例,我们创建一个 XML 扩展(一个自定义 XML 元素),它让我们能够配置 SimpleDateFormat
(来自 java.text
包)类型的对象。完成后,我们将能够按如下方式定义 SimpleDateFormat
类型的 bean 定义:
For a unified example, we create an
XML extension (a custom XML element) that lets us configure objects of the type
SimpleDateFormat
(from the java.text
package). When we are done,
we will be able to define bean definitions of type SimpleDateFormat
as follows:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
(我们稍后将在本附录中包含更加详细的示例。此第一个简单示例的目的是带你了解创建自定义扩展的基本步骤。)
(We include much more detailed examples follow later in this appendix. The intent of this first simple example is to walk you through the basic steps of making a custom extension.)
Authoring the Schema
创建用于与 Spring 的 IoC 容器一起使用的 XML 配置扩展首先从创作一个 XML Schema 以描述扩展开始。对于我们的示例,我们使用以下架构来配置 SimpleDateFormat
对象:
Creating an XML configuration extension for use with Spring’s IoC container starts with
authoring an XML Schema to describe the extension. For our example, we use the following schema
to configure SimpleDateFormat
objects:
<!-- 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 | The indicated line contains an extension base for all identifiable tags
(meaning they have an id attribute that we can use as the bean identifier in the
container). We can use this attribute because we imported the Spring-provided
beans namespace. |
前面的架构让我们能够通过使用 myns:dateformat
元素直接在一个 XML 应用程序上下文文件配置 SimpleDateFormat
对象,如下面的示例所示:
The preceding schema lets us configure SimpleDateFormat
objects directly in an
XML application context file by using the <myns:dateformat/>
element, as the following
example shows:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
请注意,在我们创建了基础设施类之后,前面的 XML 代码片段本质上与以下 XML 代码片段相同:
Note that, after we have created the infrastructure classes, the preceding snippet of XML is essentially the same as the following XML snippet:
<bean id="dateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm"/>
<property name="lenient" value="true"/>
</bean>
前面两个片段的第二个片段会在容器中创建(由简称为`SimpleDateFormat` 的名称 dateFormat
识别)一个 bean,其中设置了一些属性。
The second of the two preceding snippets
creates a bean in the container (identified by the name dateFormat
of type
SimpleDateFormat
) with a couple of properties set.
用于创建配置格式的基于架构的方法支持与具有识别架构的 XML 编辑器的 IDE 的紧密集成。通过使用经适当地创作的架构,您可以使用自动完成功能使用户在枚举中定义的不同配置选项之间进行选择。 |
The schema-based approach to creating configuration format allows for tight integration with an IDE that has a schema-aware XML editor. By using a properly authored schema, you can use autocompletion to let a user choose between several configuration options defined in the enumeration. |
Coding a NamespaceHandler
除了架构之外,我们需要一个 NamespaceHandler
来解析 Spring 在解析配置文件时遇到的此特定命名空间的所有元素。对于此示例,NamespaceHandler
应当负责解析 myns:dateformat
元素。
In addition to the schema, we need a NamespaceHandler
to parse all elements of
this specific namespace that Spring encounters while parsing configuration files. For this example, the
NamespaceHandler
should take care of the parsing of the myns:dateformat
element.
NamespaceHandler
接口具有三种方法:
The NamespaceHandler
interface features three methods:
-
init()
: Allows for initialization of theNamespaceHandler
and is called by Spring before the handler is used. -
BeanDefinition parse(Element, ParserContext)
: Called when Spring encounters a top-level element (not nested inside a bean definition or a different namespace). This method can itself register bean definitions, return a bean definition, or both. -
BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)
: Called when Spring encounters an attribute or nested element of a different namespace. The decoration of one or more bean definitions is used (for example) with the scopes that Spring supports. We start by highlighting a simple example, without using decoration, after which we show decoration in a somewhat more advanced example.
虽然你可以为整个命名空间编写自己的 NamespaceHandler
(因而提供解析该命名空间中的每个元素的代码),但经常会出现 Spring XML 配置文件中的每个顶层 XML 元素而导致一个 bean 定义(例如我们的 myns:dateformat
元素导致了一个 SimpleDateFormat
bean 定义)。Spring 具有支持此方案的许多便捷类。在以下示例中,我们使用 NamespaceHandlerSupport
类:
Although you can code your own NamespaceHandler
for the entire
namespace (and hence provide code that parses each and every element in the namespace),
it is often the case that each top-level XML element in a Spring XML configuration file
results in a single bean definition (as in our case, where a single <myns:dateformat/>
element results in a single SimpleDateFormat
bean definition). Spring features a
number of convenience classes that support this scenario. In the following example, we
use the NamespaceHandlerSupport
class:
-
Java
-
Kotlin
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
}
}
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class MyNamespaceHandler : NamespaceHandlerSupport {
override fun init() {
registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
}
}
你可能会注意到此类中实际上没有许多解析逻辑。事实上,NamespaceHandlerSupport
类具有一个内置的委托概念。它支持注册任意数量的 BeanDefinitionParser
实例,当它需要解析其命名空间中的一个元素时,它会委托给此类实例。此种清晰的分离方案让 NamespaceHandler
处理其命名空间中所有自定义元素的解析编排,同时委托给`BeanDefinitionParsers` 来执行 XML 解析的基本工作。这意味着每个 BeanDefinitionParser
仅包含用于解析单个自定义元素的逻辑,如我们在下一步中看到的。
You may notice that there is not actually a whole lot of parsing logic
in this class. Indeed, the NamespaceHandlerSupport
class has a built-in notion of
delegation. It supports the registration of any number of BeanDefinitionParser
instances, to which it delegates to when it needs to parse an element in its
namespace. This clean separation of concerns lets a NamespaceHandler
handle the
orchestration of the parsing of all of the custom elements in its namespace while
delegating to BeanDefinitionParsers
to do the grunt work of the XML parsing. This
means that each BeanDefinitionParser
contains only the logic for parsing a single
custom element, as we can see in the next step.
Using BeanDefinitionParser
如果 NamespaceHandler
遇到了已被映射到特定 bean 定义解析器(本例中为 dateformat
)的类型的 XML 元素,则会用到 BeanDefinitionParser
。换句话说,BeanDefinitionParser
负责解析架构中定义的某个不同的顶层 XML 元素。在解析器中,我们可以访问 XML 元素(因而也可以访问其子元素),以便能够解析我们的自定义 XML 内容,如你在以下示例中看到的:
A BeanDefinitionParser
is used if the NamespaceHandler
encounters an XML
element of the type that has been mapped to the specific bean definition parser
(dateformat
in this case). In other words, the BeanDefinitionParser
is
responsible for parsing one distinct top-level XML element defined in the schema. In
the parser, we' have access to the XML element (and thus to its subelements, too) so that
we can parse our custom XML content, as you can see in the following example:
- Java
-
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 | We use the Spring-provided AbstractSingleBeanDefinitionParser to handle a lot of
the basic grunt work of creating a single BeanDefinition . |
2 | We supply the AbstractSingleBeanDefinitionParser superclass with the type that our
single BeanDefinition represents.
|
3 | We use the Spring-provided AbstractSingleBeanDefinitionParser to handle a lot of
the basic grunt work of creating a single BeanDefinition . |
4 | We supply the AbstractSingleBeanDefinitionParser superclass with the type that our
single BeanDefinition represents. |
在此简单示例中,这是我们唯一需要执行的操作。创建我们的单个`BeanDefinition` 由父类 AbstractSingleBeanDefinitionParser
处理,bean 定义的唯一标识符的提取和设置也是如此。
In this simple case, this is all that we need to do. The creation of our single
BeanDefinition
is handled by the AbstractSingleBeanDefinitionParser
superclass, as
is the extraction and setting of the bean definition’s unique identifier.
Registering the Handler and the Schema
编码已完成。剩余的就是让 Spring XML 解析基础设施感知我们的自定义元素。我们通过在两个特殊用途属性文件中注册我们的自定义 namespaceHandler
和自定义 XSD 文件来执行此操作。这些属性文件都放置在你的应用程序中的 META-INF
目录中,并且可以例如随你 JAR 文件中的二进制类一起分发。Spring XML 解析基础设施通过使用这些特殊属性文件(它们的格式在下两节中详细介绍)自动选择你的新扩展。
The coding is finished. All that remains to be done is to make the Spring XML
parsing infrastructure aware of our custom element. We do so by registering our custom
namespaceHandler
and custom XSD file in two special-purpose properties files. These
properties files are both placed in a META-INF
directory in your application and
can, for example, be distributed alongside your binary classes in a JAR file. The Spring
XML parsing infrastructure automatically picks up your new extension by consuming
these special properties files, the formats of which are detailed in the next two sections.
Writing META-INF/spring.handlers
名为 spring.handlers
的属性文件包含 XML Schema URI 到命名空间处理程序类的映射。对于我们的示例,我们需要编写以下内容:
The properties file called spring.handlers
contains a mapping of XML Schema URIs to
namespace handler classes. For our example, we need to write the following:
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(:
字符是 Java 属性格式中的一个有效分隔符,因此 URI 中的`:` 字符需要使用反斜线转义。)
(The :
character is a valid delimiter in the Java properties format, so
:
character in the URI needs to be escaped with a backslash.)
键值对中的第一部分(键)是你自定义命名空间扩展关联的 URI,它需要与在你的自定义 XSD 架构中指定的 targetNamespace
属性值完全匹配。
The first part (the key) of the key-value pair is the URI associated with your custom
namespace extension and needs to exactly match exactly the value of the targetNamespace
attribute, as specified in your custom XSD schema.
Writing 'META-INF/spring.schemas'
名为 spring.schemas
的属性文件包含了 XML 模式位置(在使用该模式作为 xsi:schemaLocation
属性一部分的 XML 中,将模式声明与该模式一起引用)到类路径资源的映射。需要这个文件来防止 Spring 绝对必须使用需要互联网访问才能检索模式文件的默认 EntityResolver
。如果你在此属性文件中指定了映射,Spring 就会在类路径上搜索该模式(在本例中为 org.springframework.samples.xml
包中的 myns.xsd
)。以下代码段显示了我们为我们的自定义模式需要添加的行:
The properties file called spring.schemas
contains a mapping of XML Schema locations
(referred to, along with the schema declaration, in XML files that use the schema as part
of the xsi:schemaLocation
attribute) to classpath resources. This file is needed
to prevent Spring from absolutely having to use a default EntityResolver
that requires
Internet access to retrieve the schema file. If you specify the mapping in this
properties file, Spring searches for the schema (in this case,
myns.xsd
in the org.springframework.samples.xml
package) on the classpath.
The following snippet shows the line we need to add for our custom schema:
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(请记住,必须转义 :
字符。)
(Remember that the :
character must be escaped.)
我们建议你将你的 XSD 文件(或文件)与类路径上的 NamespaceHandler
和 BeanDefinitionParser
类一起部署。
You are encouraged to deploy your XSD file (or files) right alongside
the NamespaceHandler
and BeanDefinitionParser
classes on the classpath.
Using a Custom Extension in Your Spring XML Configuration
使用你自己实现的自定义扩展与使用 Spring 提供的“custom
”扩展之一没有区别。以下示例在 Spring XML 配置文件中使用了前面步骤中开发的自定义 <dateformat/>
元素:
Using a custom extension that you yourself have implemented is no different from using
one of the “custom” extensions that Spring provides. The following
example uses the custom <dateformat/>
element developed in the previous steps
in a Spring XML configuration file:
<?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 | Our custom bean. |
More Detailed Examples
本节介绍了一些自定义 XML 扩展的更详细的示例。
This section presents some more detailed examples of custom XML extensions.
Nesting Custom Elements within Custom Elements
本节中给出的示例说明了如何编写要满足以下配置目标所需的各种神器:
The example presented in this section shows how you to write the various artifacts required to satisfy a target of the following configuration:
<?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
类:
The preceding configuration nests custom extensions within each other. The class
that is actually configured by the <foo:component/>
element is the Component
class (shown in the next example). Notice how the Component
class does not expose a
setter method for the components
property. This makes it hard (or rather impossible)
to configure a bean definition for the Component
class by using setter injection.
The following listing shows the Component
class:
-
Java
-
Kotlin
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;
}
}
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
:
The typical solution to this issue is to create a custom FactoryBean
that exposes a
setter property for the components
property. The following listing shows such a custom
FactoryBean
:
-
Java
-
Kotlin
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;
}
}
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 管道隐藏起来。如果我们坚持使用 the steps described previously,那么我们首先要创建 XSD 模式,以此来定义自定义标记的结构,如下面清单所示:
This works nicely, but it exposes a lot of Spring plumbing to the end user. What we are going to do is write a custom extension that hides away all of this Spring plumbing. If we stick to the steps described previously, we start off by creating the XSD schema to define the structure of our custom tag, as the following listing shows:
<?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>
与 the process described earlier一样,然后我们创建一个自定义 NamespaceHandler
:
Again following the process described earlier,
we then create a custom NamespaceHandler
:
-
Java
-
Kotlin
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
}
}
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class ComponentNamespaceHandler : NamespaceHandlerSupport() {
override fun init() {
registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
}
}
接下来是自定义 BeanDefinitionParser
。请记住,我们正在创建一个描述 ComponentFactoryBean
的 BeanDefinition
。以下清单显示了我们的自定义 BeanDefinitionParser
实现:
Next up is the custom BeanDefinitionParser
. Remember that we are creating
a BeanDefinition
that describes a ComponentFactoryBean
. The following
listing shows our custom BeanDefinitionParser
implementation:
-
Java
-
Kotlin
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);
}
}
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 基础设施注册各种神器,如下所示:
Finally, the various artifacts need to be registered with the Spring XML infrastructure,
by modifying the META-INF/spring.handlers
and META-INF/spring.schemas
files, as follows:
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
Custom Attributes on “Normal” Elements
编写自己的自定义解析器和相关神器并不难。但是,有时这样做并不是正确的选择。考虑一种场景,你需要向已存在的 Bean 定义添加元数据。在这种情况下,你肯定不想编写自己的整个自定义扩展名。相反,你只需要向现有的 Bean 定义元素添加一个附加属性。
Writing your own custom parser and the associated artifacts is not hard. However, it is sometimes not the right thing to do. Consider a scenario where you need to add metadata to already existing bean definitions. In this case, you certainly do not want to have to write your own entire custom extension. Rather, you merely want to add an additional attribute to the existing bean definition element.
通过另一个示例,假设你为服务对象定义了一个 Bean 定义,该对象(自己不知道)访问一个集群 https://www.jcp.org/en/jsr/detail?id=107 [JCache],并且你希望确保命名的 JCache 实例在周围集群中急切启动。以下清单显示了这样的定义:
By way of another example, suppose that you define a bean definition for a service object that (unknown to it) accesses a clustered JCache, and you want to ensure that the named JCache instance is eagerly started within the surrounding cluster. The following listing shows such a definition:
<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
:
We can then create another BeanDefinition
when the
’jcache:cache-name'` attribute is parsed. This BeanDefinition
then initializes
the named JCache for us. We can also modify the existing BeanDefinition
for the
’checkingAccountService'` so that it has a dependency on this new
JCache-initializing BeanDefinition
. The following listing shows our JCacheInitializer
:
-
Java
-
Kotlin
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...
}
}
class JCacheInitializer(private val name: String) {
fun initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
现在,我们可以转到自定义扩展名了。首先,我们需要编写描述自定义属性的 XSD 架构,如下所示:
Now we can move onto the custom extension. First, we need to author the XSD schema that describes the custom attribute, as follows:
<?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
,如下所示:
Next, we need to create the associated NamespaceHandler
, as follows:
-
Java
-
Kotlin
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
new JCacheInitializingBeanDefinitionDecorator());
}
}
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class JCacheNamespaceHandler : NamespaceHandlerSupport() {
override fun init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
JCacheInitializingBeanDefinitionDecorator())
}
}
接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析 XML 属性,所以我们编写 BeanDefinitionDecorator
而不是 BeanDefinitionParser
。以下清单显示了我们的 BeanDefinitionDecorator
实现:
Next, we need to create the parser. Note that, in this case, because we are going to parse
an XML attribute, we write a BeanDefinitionDecorator
rather than a BeanDefinitionParser
.
The following listing shows our BeanDefinitionDecorator
implementation:
-
Java
-
Kotlin
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;
}
}
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 基础设施,如下所示:
Finally, we need to register the various artifacts with the Spring XML infrastructure
by modifying the META-INF/spring.handlers
and META-INF/spring.schemas
files, as follows:
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