20200106 Spring官方文档(Core 9)

9.附录

9.1。XML模式

9.1.1。util模式

util标记处理常见的实用程序配置问题,例如配置集合,引用常量等。

util schema:

<?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:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">

        <!-- bean definitions here -->

</beans>

使用 util:constant/

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

等价于

<bean id="..." class="...">
    <property name="isolation">
        <util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
    </property>
</bean>

从字段值设置Bean属性或构造函数参数

FieldRetrievingFactoryBean是一个FactoryBean,它检索静态或非静态字段值。 它通常用于检索公共静态最终常量,然后可以将其用于为另一个bean设置属性值或构造函数参数。

以下示例显示如何使用staticField属性公开静态字段:

<bean id="myField"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>

这的确意味着不再需要选择bean id是什么(因此,引用它的任何其他bean也必须使用这个较长的名称),但是这种形式的定义非常简洁,可以很方便地用作内部bean对象。因为不必为bean引用指定id,如以下示例所示:

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

您还可以访问另一个bean的非静态(实例)字段,如FieldRetrievingFactoryBean类的API文档中所述。

在Spring中,很容易将枚举值作为属性或构造函数参数注入到bean中。 实际上,您不必做任何事情或不了解Spring内部信息(甚至不了解诸如FieldRetrievingFactoryBean之类的类)也不知道任何事情。 以下示例枚举显示了注入枚举值的难易程度:

package javax.persistence;

public enum PersistenceContextType {

    TRANSACTION,
    EXTENDED
}

现在考虑以下类型的PersistenceContextType setter和相应的bean定义:

package example;

public class Client {

    private PersistenceContextType persistenceContextType;

    public void setPersistenceContextType(PersistenceContextType type) {
        this.persistenceContextType = type;
    }
}
<bean class="example.Client">
    <property name="persistenceContextType" value="TRANSACTION"/>
</bean>

使用 util:property-path/

<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

前面的配置使用Spring FactoryBean实现(PropertyPathFactoryBean)创建一个名为testBean.age的bean(类型为int),其值等于testBean bean的age属性。

现在考虑以下示例,该示例添加了一个<util:property-path/>元素:

<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<util:property-path id="name" path="testBean.age"/>

<property-path />元素的path属性的值遵循beanName.beanProperty的形式。 在这种情况下,它将获取名为testBean的bean的age属性。 该年龄属性的值为10。

使用util:property-path/设置bean属性或构造器参数

PropertyPathFactoryBean是一个FactoryBean,它评估给定目标对象上的属性路径。 可以直接指定目标对象,也可以通过bean名称指定目标对象。 然后,您可以在另一个bean定义中将此值用作属性值或构造函数参数。

下面的示例按名称显示了针对另一个bean的路径:

// target bean to be referenced by name
<bean id="person" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

// results in 11, which is the value of property 'spouse.age' of bean 'person'
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetBeanName" value="person"/>
    <property name="propertyPath" value="spouse.age"/>
</bean>

在以下示例中,针对内部bean评估路径:

<!-- results in 12, which is the value of property 'age' of the inner bean -->
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetObject">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="12"/>
        </bean>
    </property>
    <property name="propertyPath" value="age"/>
</bean>

还有一种快捷方式,其中Bean名称是属性路径。 以下示例显示了快捷方式:

<!-- results in 10, which is the value of property 'age' of bean 'person' -->
<bean id="person.age"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

这种形式的确意味着在bean名称中没有选择。 对它的任何引用也必须使用相同的ID,即路径。 如果用作内部bean,则根本不需要引用它,如以下示例所示:

<bean id="..." class="...">
    <property name="age">
        <bean id="person.age"
                class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
    </property>
</bean>

您可以在实际定义中专门设置结果类型。 对于大多数用例来说,这不是必需的,但有时可能很有用。 有关此功能的更多信息,请参见javadoc。

使用 util:properties/

<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>

前面的配置使用Spring FactoryBean实现(PropertiesFactoryBean)来实例化java.util.Properties实例,并从提供的Resource位置加载值。

<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>

使用 util:list/

<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList">
        <list>
            <value>pechorin@hero.org</value>
            <value>raskolnikov@slums.org</value>
            <value>stavrogin@gov.org</value>
            <value>porfiry@gov.org</value>
        </list>
    </property>
</bean>

前面的配置使用Spring FactoryBean实现(ListFactoryBean)创建一个java.util.List实例,并使用从提供的sourceList中获取的值对其进行初始化。

<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
    <value>pechorin@hero.org</value>
    <value>raskolnikov@slums.org</value>
    <value>stavrogin@gov.org</value>
    <value>porfiry@gov.org</value>
</util:list>

您还可以使用<util:list />元素上的list-class属性来显式控制实例化和填充的List的确切类型。 例如,如果我们确实需要实例化java.util.LinkedList,则可以使用以下配置:

<util:list id="emails" list-class="java.util.LinkedList">
    <value>jackshaftoe@vagabond.org</value>
    <value>eliza@thinkingmanscrumpet.org</value>
    <value>vanhoek@pirate.org</value>
    <value>d'Arcachon@nemesis.org</value>
</util:list>

如果没有提供list-class属性,则容器选择一个List实现。

使用 util:map/

<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' -->
<bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean">
    <property name="sourceMap">
        <map>
            <entry key="pechorin" value="pechorin@hero.org"/>
            <entry key="raskolnikov" value="raskolnikov@slums.org"/>
            <entry key="stavrogin" value="stavrogin@gov.org"/>
            <entry key="porfiry" value="porfiry@gov.org"/>
        </map>
    </property>
</bean>

前面的配置使用Spring FactoryBean实现(MapFactoryBean)创建一个java.util.Map实例,该实例使用从提供的“sourceMap”中获取的键值对进行初始化。

<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
    <entry key="pechorin" value="pechorin@hero.org"/>
    <entry key="raskolnikov" value="raskolnikov@slums.org"/>
    <entry key="stavrogin" value="stavrogin@gov.org"/>
    <entry key="porfiry" value="porfiry@gov.org"/>
</util:map>

您还可以通过使用<util:map />元素上的map-class属性来显式控制实例化和填充的Map的确切类型。 例如,如果我们确实需要实例化java.util.TreeMap,则可以使用以下配置:

<util:map id="emails" map-class="java.util.TreeMap">
    <entry key="pechorin" value="pechorin@hero.org"/>
    <entry key="raskolnikov" value="raskolnikov@slums.org"/>
    <entry key="stavrogin" value="stavrogin@gov.org"/>
    <entry key="porfiry" value="porfiry@gov.org"/>
</util:map>

如果没有提供map-class属性,则容器选择一个Map实现。

使用 util:set/

<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' -->
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
    <property name="sourceSet">
        <set>
            <value>pechorin@hero.org</value>
            <value>raskolnikov@slums.org</value>
            <value>stavrogin@gov.org</value>
            <value>porfiry@gov.org</value>
        </set>
    </property>
</bean>

前面的配置使用Spring FactoryBean实现(SetFactoryBean)创建一个java.util.Set实例,该实例使用从提供的sourceSet中获取的值进行初始化。

以下示例使用<util:set />元素进行更简洁的表示:

<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
    <value>pechorin@hero.org</value>
    <value>raskolnikov@slums.org</value>
    <value>stavrogin@gov.org</value>
    <value>porfiry@gov.org</value>
</util:set>

您还可以通过使用<util:set />元素上的set-class属性来显式控制实例化和填充的Set的确切类型。 例如,如果我们确实需要实例化java.util.TreeSet,则可以使用以下配置:

<util:set id="emails" set-class="java.util.TreeSet">
    <value>pechorin@hero.org</value>
    <value>raskolnikov@slums.org</value>
    <value>stavrogin@gov.org</value>
    <value>porfiry@gov.org</value>
</util:set>

如果没有提供set-class属性,则容器选择一个Set实现。

9.1.2。aop模式

这些aop标签涉及在Spring中配置所有AOP,包括Spring自己的基于代理的AOP框架以及Spring与AspectJ AOP框架的集成。

aop的schema:

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->

</beans>

9.1.3。context模式

上下文标签处理与管道(plumbing)相关的ApplicationContext配置,即通常不是对最终用户重要的bean,而是在Spring中完成大量“艰巨”工作的bean,例如BeanfactoryPostProcessors。 以下代码段引用了正确的架构,以便您可以使用上下文名称空间中的元素:

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- bean definitions here -->

</beans>

使用

该标签激活${…}占位符的替换,这些占位符针对指定的属性文件(作为Spring资源位置)解析。 此元素是为您设置PropertySourcesPlaceholderConfigurer的便捷机制。 如果您需要对特定的PropertySourcesPlaceholderConfigurer设置进行更多控制,则可以自己将其明确定义为Bean。

使用

此标签激活Spring基础结构以检测Bean类中的注释:

  • Spring 的@Configuration模型
  • @Autowired/@Inject@Value
  • JSR-250的@Resource@PostConstruct@PreDestroy(如果可用)
  • JPA @PersistenceContext@PersistenceUnit(如果可用)
  • Spring 的 @EventListener

或者,您可以选择为这些注释显式激活各个BeanPostProcessor

此标签不会激活Spring的@Transactional批注的处理; 您可以为此目的使用<tx:annotation-driven />元素。 同样,还需要显式启用Spring的缓存注释。

使用

使用

使用

使用

使用

9.1.4。Bean模式

下面的示例在周围的<bean />上下文中显示了<meta />元素(请注意,没有任何逻辑来解释它,元数据实际上是毫无用处的)。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="foo" class="x.y.Foo">
        <meta key="cacheName" value="foo"/> 
        <property name="name" value="Rick"/>
    </bean>

</beans>

在前面的示例中,您可以假设存在一些逻辑,这些逻辑消耗了bean的定义,并建立了一些使用提供的元数据的缓存基础结构。

9.2。XML模式创作

从2.0版开始,Spring提供了一种机制,该机制可将基于架构的扩展添加到用于定义和配置bean的基本Spring XML格式中。 本节介绍如何编写自己的自定义XML Bean定义解析器,以及如何将此类解析器集成到Spring IoC容器中。

为了方便使用模式识别XML编辑器编写配置文件,Spring的可扩展XML配置机制基于XML模式。

要创建新的XML配置扩展,请执行以下操作:

  1. 编写 XML模式以描述您的自定义标签。
  2. 编写自定义NamespaceHandler实现的代码。
  3. 对一个或多个BeanDefinitionParser实现进行编码(这是完成实际工作的地方)。
  4. 向Spring 注册新的工件。

对于一个统一的示例,我们创建一个XML扩展(一个自定义XML标签),该扩展使我们可以配置SimpleDateFormat类型的对象(来自java.text包)。 完成后,我们将能够定义SimpleDateFormat类型的bean定义,如下所示:

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

9.2.1。编写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"> 
                    <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>

前面的架构使我们可以使用<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-HH-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>

创建配置格式的基于schema的方法允许与具有schema识别XML编辑器的IDE紧密集成。 通过使用正确编写的架构,可以使用自动完成功能来让用户在枚举中定义的多个配置选项之间进行选择。

9.2.2。编码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类:

package org.springframework.samples.xml;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    }
}

您可能会注意到,此类中实际上没有很多解析逻辑。 实际上,NamespaceHandlerSupport类具有内置的委托概念。 它支持注册任意数量的BeanDefinitionParser实例,在需要解析其名称空间中的元素时将其委托给该实例。 这种清晰的关注点分离使NamespaceHandler可以处理其命名空间中所有自定义元素的解析编排,同时委托BeanDefinitionParsers来完成XML解析的繁琐工作。 这意味着每个BeanDefinitionParser都仅包含用于解析单个自定义元素的逻辑,正如我们在下一步中看到的那样。

9.2.3。使用BeanDefinitionParser

如果NamespaceHandler遇到映射到特定bean定义解析器(在这种情况下为dateformat)的类型的XML元素,则使用BeanDefinitionParser。 换句话说,BeanDefinitionParser负责解析模式中定义的一个不同的顶级XML元素。 在解析器中,我们可以访问XML元素(因此也可以访问其子元素),以便我们可以解析自定义XML内容,如以下示例所示:

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 { 

    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class; 
    }

    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));
        }
    }

}

在这种简单的情况下,这就是我们要做的。 单个BeanDefinition的创建由AbstractSingleBeanDefinitionParser超类处理,bean定义的唯一标识符的提取和设置也是如此。

9.2.4。注册处理程序和架构

编码完成。 剩下要做的就是让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模式位置(与模式声明一起引用,在XML文件中将模式用作xsi:schemaLocation属性的一部分)与类路径资源的映射。 需要使用该文件来防止Spring绝对使用默认的EntityResolver,该默认的EntityResolver需要Internet访问才能检索架构文件。 如果在此属性文件中指定映射,则Spring将在类路径上搜索架构(在本例中为org.springframework.samples.xml包中的myns.xsd)。 以下代码段显示了我们需要为自定义架构添加的行:

http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd

(请记住 : 字符必须转义。)

鼓励您在类路径的NamespaceHandlerBeanDefinitionParser类旁边部署XSD文件。

9.2.5。在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"/> 

    <bean id="jobDetailTemplate" abstract="true">
        <property name="dateFormat">
            <!-- as an inner bean -->
            <myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
        </property>
    </bean>

</beans>

9.2.6。更详细的例子

在自定义元素中嵌套自定义元素

<?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类:

package com.foo;

import java.util.ArrayList;
import java.util.List;

public class Component {

    private String name;
    private List<Component> components = new ArrayList<Component> ();

    // mmm, 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;
    }
}

解决此问题的典型方法是创建一个自定义FactoryBean,它公开了component属性的setter属性。 以下清单显示了这样的自定义FactoryBean:

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;
    }
}

这很好用,但是向最终用户暴露了很多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

package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
    }
}

接下来是自定义BeanDefinitionParser。 请记住,我们正在创建一个描述ComponentFactoryBean的BeanDefinition。 以下清单显示了我们的自定义BeanDefinitionParser实现:

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<BeanDefinition>(childElements.size());
        for (Element element : childElements) {
            children.add(parseComponentElement(element));
        }
        factory.addPropertyValue("children", children);
    }
}

最后,需要通过修改META-INF / spring.handlersMETA-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定义元素添加一个附加属性。

作为另一个示例,假设您为访问集群JCache的服务对象(它不知道)定义了Bean定义,并且您想确保在周围的集群中急切启动命名的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:

package com.foo;

public class JCacheInitializer {

    private String name;

    public JCacheInitializer(String name) {
        this.name = name;
    }

    public void 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,如下所示:

package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
            new JCacheInitializingBeanDefinitionDecorator());
    }

}

接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析XML属性,所以我们编写了一个BeanDefinitionDecorator而不是一个BeanDefinitionParser。以下清单显示了我们的BeanDefinitionDecorator实现:

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;
    }
}

最后,我们需要通过修改META-INF/spring.handlersMETA-INF/spring.schemas文件,在Spring XML基础结构中注册各种工件,如下所示:

#在'META-INF / spring.handlers'中
http \://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler

#在'META-INF / spring.schemas'中
http \://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
posted @ 2020-01-06 21:23  流星<。)#)))≦  阅读(229)  评论(0编辑  收藏  举报