Spring 5 中文解析之核心篇-附录

技术交流群:

在这里插入图片描述

9.1 XML Schemas

附录的此部分列出了与核心容器相关的XML schemas。

9.1.1 util Schema

顾名思义,util标签处理常见的实用程序配置问题,例如配置集合、引用常数等。要在util schema中使用标签,你需要在Spring XML配置文件的顶部具有以下序言(代码段中的文本引用了正确的schema,以便你可以使用util名称空间中的标签):

<?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定义:

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

前面的配置使用Spring FactoryBean实现(FieldRetrievingFactoryBean)将bean上的隔离性的值设置为java.sql.Connection.TRANSACTION_SERIALIZABLE常量的值。这一切都很好,但是它太冗长了,并且(不必要地)向最终用户暴露了Spring的内部管道。

以下基于XML Schema的版本更加简洁,清楚地表达了开发人员的意图(“注入这个常数值”),并且读起来更好:

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

使用<util:constant/>

考虑下面bean定义:

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

前面的配置使用Spring FactoryBean实现(FieldRetrievingFactoryBean)将bean上的隔离性的值设置为java.sql.Connection.TRANSACTION_SERIALIZABLE常量的值。这一切都很好,但是它太冗长了,并且(不必要地)向最终用户暴露了Spring的内部管道。

以下基于XML Schema的版本更加简洁,清楚地表达了开发人员的意图(“注入这个常数值”),并且读起来更好:

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

使用<util:constant/>

考虑下面bean定义:

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

前面的配置使用Spring FactoryBean实现(FieldRetrievingFactoryBean)将bean上的隔离性的值设置为java.sql.Connection.TRANSACTION_SERIALIZABLE常量的值。这一切都很好,但是它太冗长了,并且(不必要地)向最终用户暴露了Spring的内部管道。

以下基于XML Schema的版本更加简洁,清楚地表达了开发人员的意图(“注入这个常数值”),并且读起来更好:

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

使用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位置加载值。

以下示例使用util:properties元素进行更简洁的表示:

<!-- 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中获取的值对其进行初始化。

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

<!-- 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”中获取的键值对进行初始化。

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

<!-- 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.2app Schema

aop标签用于配置Spring中的所有AOP,包括Spring自己的基于代理的AOP框架以及Spring与AspectJ AOP框架的集成。这些标签在“面向切面的Spring编程”的章节中全面介绍。

为了完整起见,要在aop schema中使用标签,你需要在Spring XML配置文件的顶部具有以下序言(代码段中的文本引用了正确的schema,以便aop名称空间中的标签为提供给你):

<?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 Schema

context标签处理与ApplicationContext配置相关联,也就是说,通常不是对终端用户重要的bean,而是在Spring中执行大量繁重工作的bean,如BeanfactoryPostProcessors。以下代码段引用了正确的schema,以便你可以使用context名称空间中的元素:

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

使用<property-placeholder/>

该元素激活${…}占位符的替换,这些占位符针对指定的属性文件(作为Spring资源位置)解析。这个元素是为你设置PropertySourcesPlaceholderConfigurer的便利机制。如果你需要对特定的PropertySourcesPlaceholderConfigurer设置进行更多的控制,你可以自己显式地将其定义为bean。

使用<annotation-config/>

此元素激活Spring基础结构以检测Bean类中的注解:

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

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

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

使用<component-scan/>

有关基于注解的容器配置的部分中详细介绍了此元素。

使用<load-time-weaver/>

在Spring Framework中关于使用AspectJ进行加载时编织的部分详细介绍了此元素。

使用<spring-configured/>

关于使用AspectJ通过Spring依赖注入域对象的部分将详细介绍该元素。

使用<mbean-export/>

关于配置基于注解的MBean导出的部分中详细介绍了此元素。

9.1.4 Bean Schema

最后但并非最不重要的一点是,我们在bean schema中具有元素。自框架诞生之初,这些元素就已经出现在框架中。此处未显示Bean schema中各种元素的示例,因为它们在依赖关系和配置中非常全面地涵盖了它们(实际上,在整章中也是如此)。

请注意,你可以为 XML定义添加零个或多个键值对。用这些额外的元数据做什么(如果有的话)完全取决于你自己的自定义逻辑(通常只有在你编写自己的自定义元素时才有用,如附录XML Schema Authoring所述)。

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

<?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"/> //1
       <property name="name" value="Rick"/>
   </bean>

</beans>
  1. 这是示例元元素

在前面的示例中,你可以假设存在使用bean定义并设置使用提供的元数据的缓存基础设施的某种逻辑。

9.2 XML Schema编写

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

为了方便编写使用感知模式的XML编辑器的配置文件,Spring的可扩展XML配置机制基于XMLSchema。如果你不熟悉标准Spring发行版附带的Spring当前XML配置扩展,那么应该首先阅读名为appendix.html的附录。

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

  1. 编写XML schema以描述你的自定义元素。
  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来配置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提供的bean名称空间。

前面的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>

前面两个片段中的第二个片段在容器中创建了一个Bean(由名称SimpleDateFormat类型的dateFormat标识),并设置了几个属性。

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

9.2.2 编写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定义)。在下面的示例中,我们使用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负责解析schema中定义的一个不同的顶级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 { //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. 我们的BeanDefinition代表。

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

9.2.4 注册处理器和Schema

编码完成。剩下要做的就是让Spring XML解析基础结构了解我们的自定义元素。通过在两个专用属性文件中注册我们的自定义namespaceHandler和自定义XSD文件来实现。这些属性文件都放在应用程序的META-INF目录中,并且可以与JAR文件中的二进制类一起分发。Spring XML解析基础结构通过使用这些特殊的属性文件来自动选择你的新扩展,以下两部分将详细介绍其格式。

编写META-INF/spring.handlers

名为spring.handlers的属性文件包含XML Schema URI到名称空间处理程序类的映射。对于我们的示例,我们需要编写以下内容:

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

:字符是Java属性格式的有效分隔符,因此URI中的:字符需要用反斜杠转义。)

键-值对的第一部分(键)是与自定义名称空间扩展相关联的URI,需要与自定义XSD模式中指定的targetNamespace属性的值精确匹配。

编写 META-INF/spring.schemas

名为spring.schemas的属性文件包含XML Schema位置(与Schema声名一起引用,在XML文件中将模式用作xsi:schemaLocation属性的一部分)与类路径资源的映射。需要使用该文件来防止Spring绝对使用默认的EntityResolver,该默认的EntityResolver需要Internet访问才能schema文件。如果在此属性文件中指定映射,则Spring将在类路径上搜索schema(在本例中为org.springframework.samples.xml包中的myns.xsd)。以下代码段显示了我们需要为自定义schema添加的行:

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

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

建议你将XSD文件(或多个文件)与类路径中的NamespaceHandlerBeanDefinitionParser类一起部署。

9.2.4 注册处理器和Schema

编码完成。剩下要做的就是让Spring XML解析基础结构了解我们的自定义元素。通过在两个专用属性文件中注册我们的自定义namespaceHandler和自定义XSD文件来实现。这些属性文件都放在应用程序的META-INF目录中,并且可以与JAR文件中的二进制类一起分发。Spring XML解析基础结构通过使用这些特殊的属性文件来自动选择你的新扩展,以下两部分将详细介绍其格式。

编写META-INF/spring.handlers

名为spring.handlers的属性文件包含XML Schema URI到名称空间处理程序类的映射。对于我们的示例,我们需要编写以下内容:

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

:字符是Java属性格式的有效分隔符,因此URI中的:字符需要用反斜杠转义。)

键-值对的第一部分(键)是与自定义名称空间扩展相关联的URI,需要与自定义XSD模式中指定的targetNamespace属性的值精确匹配。

编写 META-INF/spring.schemas

名为spring.schemas的属性文件包含XML Schema位置(与Schema声名一起引用,在XML文件中将模式用作xsi:schemaLocation属性的一部分)与类路径资源的映射。需要使用该文件来防止Spring绝对使用默认的EntityResolver,该默认的EntityResolver需要Internet访问才能schema文件。如果在此属性文件中指定映射,则Spring将在类路径上搜索schema(在本例中为org.springframework.samples.xml包中的myns.xsd)。以下代码段显示了我们需要为自定义schema添加的行:

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

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

建议你将XSD文件(或多个文件)与类路径中的NamespaceHandlerBeanDefinitionParser类一起部署。

9.2.5 在你的Spring XML配置中使用自定义扩展

使用你自己实现的自定义扩展与使用Spring提供的自定义扩展没有区别。以下示例在Spring 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: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

参考例子:com.liyong.ioccontainer.starter.MySchemaIocContainer

9.2.6 更多详细例子

本节提供一些更详细的自定义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类:

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

<?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.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定义元素添加一个附加属性。

作为另一个示例,假设你为访问集群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基础结构中注册各种工件,如下所示:

# 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
作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。

博客地址:http://youngitman.tech

CSDN:https://blog.csdn.net/liyong1028826685

微信公众号:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青年IT男

您的打赏就是对我的肯定!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值