20220507 Core - 10. Appendix

前言

文档地址

XML 模式

util 模式

util 标签处理常见的实用工具配置问题,例如配置集合、引用常量等。要使用 util 模式中的标签,您需要在 Spring XML 配置文件的顶部包含 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 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 是检索 static 或非静态字段值的 FactoryBean 。它通常用于检索 public static final 常量,然后可用于为另一个 bean 设置属性值或构造函数参数。

以下示例显示了如何使用 staticField 属性公开 static 字段

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

还有一个方便的用法,可以将 static 字段指定为 bean 名称,如下例所示:

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

这确实意味着不再需要选择 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 中很容易。

package javax.persistence;

public enum PersistenceContextType {

    TRANSACTION,
    EXTENDED
}
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/>

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

<!-- 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"/>

简化为:

<!-- 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"/>
使用 <util:property-path/> 设置 bean 属性或构造器参数

PropertyPathFactoryBean 是计算给定目标对象上的属性路径的 FactoryBean 。目标对象可以直接指定,也可以通过 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>

您可以在实际定义中具体设置结果类型。这对于大多数用例来说不是必需的,但有时可能很有用。

使用 <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 ) 来实例化具有从提供的 Resource 位置加载的值的 java.util.Properties 实例。

使用 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 属性显式控制实例化和填充的确切类型 <util: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 属性显式控制实例化和填充的确切类型 <util: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 属性显式控制实例化和填充的确切类型 <util: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 实现。

aop 模式

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>

context 模式

context 标签处理 ApplicationContext 配置,通常不是对最终用户很重要的 bean ,而是在 Spring 中执行大量内部工作的 bean ,比如 BeanFactory 后置处理器 BeanfactoryPostProcessors

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 Resources)进行解析。此标签是一种为您设置 PropertySourcesPlaceholderConfigurer 的便利机制。如果您需要对特定 PropertySourcesPlaceholderConfigurer 设置进行更多控制 ,您可以自己将其显式定义为 bean。

使用 <annotation-config/>

此标签激活 Spring 基础设施以检测 bean 类中的注解:

  • Spring 的 @Configuration
  • @Autowired/@Inject , @Value@Lookup
  • JSR-250 的 @Resource@PostConstruct@PreDestroy
  • JAX-WS @WebServiceRef 和 EJB 3 @EJB
  • JPA @PersistenceContext@PersistenceUnit
  • Spring 的 @EventListener

或者,您可以选择为这些注解显式激活单独的 BeanPostProcessors

该标签不会激活 Spring 的 @Transactional 注解处理,您可以使用 <tx:annotation-driven/> 标签。同样,Spring 的 缓存注解也需要显式 启用

使用 <component-scan/>

该标签在 基于注解的容器配置 一节中有详细说明。

使用 <load-time-weaver/>

在 Spring Framework 中 使用 AspectJ 进行加载时编织 的部分中详细介绍了此标签

使用 <spring-configured/>

使用 AspectJ 通过 Spring 依赖注入域对象 的部分中有详细说明

使用 <mbean-export/>

此元素在 配置基于注解的 MBean 导出 部分中有详细说明

beans 模式

您可以向 <bean/> XML 定义添加零个或多个键值对。如果有的话,用这个额外的元数据做什么完全取决于您自己的自定义逻辑

以下示例显示了 <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>

自定义XML 模式

本节介绍如何编写您自己的自定义 XML bean 定义解析器并将此类解析器集成到 Spring IoC 容器中。

创建新的 XML 配置扩展:

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

作为一个例子,我们创建了一个 XML 扩展(一个自定义的 XML 标签),它允许我们配置 java.text.SimpleDateFormat 类型的对象。完成后,我们将能够定义 SimpleDateFormat 类型的 bean 定义如下:

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

创建模式

创建用于 Spring 的 IoC 容器的 XML 配置扩展从编写 XML 模式来描述扩展开始。对于示例,使用以下架构来配置 SimpleDateFormat 对象:

<!-- myns.xsd (inside package org/springframework/samples/xml) -->

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://www.mycompany.example/schema/myns"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/beans"/>

    <xsd:element name="dateformat">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType"> 
                    <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>

编写 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 只包含解析单个自定义标签的逻辑,正如我们在下一步中看到的那样。

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

}

注册处理程序(Handler)和模式(Schema)

编码完成。剩下要做的就是让 Spring XML 解析基础设施知道我们的自定义标签。我们通过在两个特殊用途的属性文件中注册我们的自定义 namespaceHandler 和自定义 XSD 文件来实现。这些属性文件都放置在应用程序的 META-INF 目录中,例如,可以在 JAR 文件中与二进制类一起分发。Spring XML 解析基础设施通过使用这些特殊的属性文件来自动选择您的新扩展

编写 META-INF/spring.handlers

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

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

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

键值对的第一部分(键)是与自定义命名空间扩展关联的 URI,需要与自定义 XSD 架构中指定的 targetNamespace 属性值完全匹配。

编写 META-INF/spring.schemas

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

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

鼓励您将 XSD 文件(或多个文件)与类路径上的 NamespaceHandlerBeanDefinitionParser 类一起部署。

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

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

</beans>

更详细的例子

在自定义标签中嵌套自定义标签
<?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;
    }
}

此问题的典型解决方案是创建一个公开 components 属性的 setter 属性的自定义 FactoryBean 。以下清单显示了这样的自定义 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 。请记住,我们正在创建一个描述 ComponentFactoryBeanBeanDefinition 。以下清单显示了我们的自定义 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);
    }
}

最后,需要将各种工件注册到 Spring XML 基础架构中,通过修改 META-INF/spring.handlersMETA-INF/spring.schemas 文件,如下:

# 在“META-INF/spring.handlers”中
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler

# 在 'META-INF/spring.schema' 中
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
“普通”元素的自定义属性

编写自定义解析器和相关的工件并不难。然而,有时这不是正确的做法。考虑一个场景,您需要将元数据添加到现有的 bean 定义中。在这种情况下,您当然不想编写自己的整个自定义扩展。相反,您只想向现有 bean 定义元素添加一个附加属性。

再举一个例子,假设您为一个服务对象定义了一个 bean 定义,该服务对象访问集群 JCache,并且您想确保命名的 JCache 实例在周围的集群中急切地启动。以下清单显示了这样的定义:

<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
        jcache:cache-name="checking.account">
    <!-- other dependencies here... -->
</bean>

然后我们可以在解析 jcache:cache-name 属性时创建另一个 BeanDefinitionBeanDefinition 为我们初始化命名的 JCache 。我们还可以修改现有 checkingAccountServiceBeanDefinition , 以便它依赖于这个新的 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.schema' 中
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd

应用程序启动步骤(Application Startup Steps)

附录的这一部分列出了核心容器所使用的现有 StartupSteps

每个启动步骤的名称和详细信息不属于公共契约的一部分,可能会发生变化;这被视为核心容器的实现细节,并将跟随其行为变化。

名称 描述 Tags
spring.beans.instantiate bean 及其依赖项的实例化 beanName :bean 的名称
beanType :注入点所需的类型
spring.beans.smart-initialize SmartInitializingSingleton bean 的初始化 beanName :bean 的名称
spring.context.annotated-bean-reader.create AnnotatedBeanDefinitionReader 的创建
spring.context.base-packages.scan 扫描基本包 packages :扫描的基本包数组
spring.context.beans.post-process bean 后置处理阶段
spring.context.bean-factory.post-process BeanFactoryPostProcessor bean 的调用 postProcessor : 当前的后置处理器
spring.context.beandef-registry.post-process BeanDefinitionRegistryPostProcessor bean 的调用 postProcessor : 当前的后置处理器
spring.context.component-classes.register 通过 AnnotationConfigApplicationContext#register 注册的容器类 classes :注册的给定类的数组
spring.context.config-classes.enhance 使用 CGLIB 代理增强配置类 classCount :增强类的计数
spring.context.config-classes.parse 使用 ConfigurationClassPostProcessor 的配置类解析阶段 classCount :已处理类的计数
spring.context.refresh 应用程序上下文刷新阶段
posted @ 2022-06-09 21:17  流星<。)#)))≦  阅读(33)  评论(0编辑  收藏  举报