6.spring framework 5.2.3.RELEASE 文档之核心技术(Core Technologies) 第五篇

目录

 

1.7. Bean定义继承

1.8. 容器的扩展点

1.8.1.使用BeanPostProcessor定制bean

1.8.2. 使用BeanFactoryPostProcessor自定义配置元数据

1.8.3.  使用 FactoryBean 自定义实例化逻辑


1.7. Bean定义继承

bean定义可以包含很多配置信息,包括构造函数参数、属性值和容器特定的信息,例如初始化方法、静态工厂方法名等等。子bean定义从父定义继承配置数据。子定义可以根据需要重写某些值或添加其他值。使用父bean和子bean定义可以节省很多类型。实际上,这是模板化的一种形式。

如果以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户不在这个级别上与他们一起工作。相反,它们在类(如ClassPathXmlApplicationContext)中声明性地配置bean定义。使用基于XML的配置元数据时,可以通过使用parent属性来指示子bean定义,并将父bean指定为该属性的值。下面的示例演示了如何执行此操作:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

注意inheritsWithDifferentClass Bean定义的parent属性

如果未指定子bean定义,则使用父定义中的bean类,但也可以覆盖它。 在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。

子bean定义从父bean继承作用域、构造函数参数值、属性值和方法重写,并具有添加新值的选项。指定的任何范围、初始化方法、销毁方法或静态工厂方法设置都将覆盖相应的父设置。

其余设置始终取自子定义:依赖项、自动装配模式、依赖项检查、单例和延迟初始化。

前面的示例使用abstract属性显式地将父bean定义标记为抽象。如果父定义没有指定类,则需要显式地将父bean定义标记为抽象,如下例所示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

父bean不能单独实例化,因为它不完整,并且还被明确标记为抽象。 当定义是抽象的时,它只能用作纯模板bean定义,用作子定义的父定义。 尝试通过将其称为另一个bean的ref属性来单独使用这样的抽象父bean或使用父bean ID进行显式的getBean()调用会返回错误。 同样,容器的内部preInstantiateSingletons()方法将忽略定义为抽象的bean定义。

默认情况下,ApplicationContext预先实例化所有单例。因此,重要的是(至少对于singleton bean而言),如果您有一个(父)bean定义,而您只打算将其用作模板,并且该定义指定了一个类,那么您必须确保将abstract属性设置为true,否则应用程序上下文将实际(试图)预先实例化该抽象bean。

1.8. 容器的扩展点

通常,应用程序开发人员不需要为ApplicationContext实现类子类。相反,Spring IoC容器可以通过插入特殊集成接口的实现来扩展。接下来的几节将描述这些集成接口。

1.8.1.使用BeanPostProcessor定制bean

BeanPostProcessor接口定义了回调方法,您可以实施这些回调方法以提供自己的(或覆盖容器的默认值)实例化逻辑,依赖关系解析逻辑等。 如果您想在Spring容器完成实例化,配置和初始化bean之后实现一些自定义逻辑,则可以插入一个或多个自定义BeanPostProcessor实现。

您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。 仅当BeanPostProcessor实现Ordered接口时,才可以设置此属性。 如果您编写自己的BeanPostProcessor,则也应该考虑实现Ordered接口。 有关更多详细信息,请参见BeanPostProcessor和Ordered接口的javadoc。 另请参见有关以编程方式注册BeanPostProcessor实例的说明

BeanPostProcessor实例在bean(或对象)实例上运行。 也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例完成其工作。

 

BeanPostProcessor实例是按容器划分作用域的。 仅在使用容器层次结构时,这才有意义。 如果在一个容器中定义BeanPostProcessor,它将仅对该容器中的bean进行后处理。 换句话说,一个容器中定义的Bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使这两个容器是同一层次结构的一部分。

 

要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数据中所述。

org.springframework.beans.factory.config.BeanPostProcessor接口正好由两个回调方法组成。当这样的类注册为容器的后处理器时,对于容器创建的每个bean实例,后处理器在调用容器初始化方法(如initializengbean.afterPropertiesSet()或任何声明的init方法)之前和任何bean初始化之后从容器获取回调回拨。后处理器可以对bean实例执行任何操作,包括完全忽略回调。bean后处理器通常检查回调接口,或者它可以用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean后处理器。

ApplicationContext会自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。ApplicationContext将这些bean注册为后处理器,以便以后在创建bean时调用它们。Bean后处理器可以以与任何其他Bean相同的方式部署在容器中。

注意,在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口。 指示该bean的后处理器性质。 否则,ApplicationContext无法在完全创建之前按类型自动检测它。 由于BeanPostProcessor需要及早实例化才能应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。

以编程方式注册BeanPostProcessor实例

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如前所述),但您可以使用addBeanPostProcessor方法对ConfigurableBeanFactory以编程方式注册它们。当您需要在注册之前评估条件逻辑,甚至需要跨层次结构中的上下文复制bean后处理器时,这一点非常有用。但是,请注意,以编程方式添加的BeanPostProcessor实例不尊重有序接口。在这里,登记的顺序决定了执行的顺序。还要注意,无论显式排序如何,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前进行处理。

BeanPostProcessor实例和AOP自动代理

BeanPostProcessor实例和AOP自动代理实现BeanPostProcessor接口的类是特殊的,容器对它们的处理方式不同。所有BeanPostProcessor实例和它们直接引用的bean都在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,所有BeanPostProcessor实例都以排序的方式注册,并应用于容器中的所有其他bean。由于AOP自动代理是作为beanpstprocessor本身实现的,因此beanpstprocessor实例或它们直接引用的bean都不符合自动代理的条件,因此,它们中没有编织的方面。

 

对于任何这样的bean,您都应该看到一条信息日志消息:bean someBean不适合由所有beanpstprocessor接口处理(例如:不适合自动代理)。

 

如果您使用自动装配或@Resource(可能会退回到自动装配)将Bean连接到BeanPostProcessor中,则Spring在搜索类型匹配的依赖项候选对象时可能会访问意外的Bean,因此使它们不符合自动代理或其他种类的条件 豆后处理。 例如,如果您有一个用@Resource注解的依赖项,其中字段或设置器名称不直接与bean的声明名称相对应,并且不使用name属性,那么Spring将访问其他bean以按类型匹配它们。

下面的示例演示如何在ApplicationContext中编写,注册和使用BeanPostProcessor实例。

Example: Hello World, BeanPostProcessor-style

第一个示例说明了基本用法。 该示例显示了一个自定义BeanPostProcessor实现,该实现调用由容器创建的每个bean的toString()方法,并将结果字符串打印到系统控制台。

以下清单显示了自定义BeanPostProcessor实现类的定义:

java 示例代码

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

kotlin示例代码

import org.springframework.beans.factory.config.BeanPostProcessor

class InstantiationTracingBeanPostProcessor : BeanPostProcessor {

    // simply return the instantiated bean as-is
    override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
        return bean // we could potentially return any object reference here...
    }

    override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
        println("Bean '$beanName' created : $bean")
        return bean
    }
}

以下bean元素使用InstantiationTracingBeanPostProcessor:

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

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

注意如何仅定义InstantiationTracingBeanPostProcessor。 它甚至没有名称,并且因为它是Bean,所以可以像其他任何Bean一样对其进行依赖注入。 (前面的配置还定义了一个由Groovy脚本支持的bean。Spring动态语言支持在标题为Dynamic Language Support的一章中有详细介绍。)

以下Java应用程序运行上述代码和配置:

java 示例代码

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

kotlin示例代码

import org.springframework.beans.factory.getBean

fun main() {
    val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
    val messenger = ctx.getBean<Messenger>("messenger")
    println(messenger)
}

前面的应用程序的输出类似于以下内容:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

Example: RequiredAnnotationBeanPostProcessor

将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。 Spring的RequiredAnnotationBeanPostProcessor是一个示例,它是Spring发行版附带的BeanPostProcessor实现,可确保标有(任意)批注的bean上的JavaBean属性实际上(被配置为)依赖注入了一个值。

1.8.2. 使用BeanFactoryPostProcessor自定义配置元数据

我们要看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。 该接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor对Bean配置元数据进行操作。 也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并有可能在容器实例化除BeanFactoryPostProcessor实例以外的任何bean之前更改它。

您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。 但是,仅当BeanFactoryPostProcessor实现Ordered接口时,才可以设置此属性。 如果您编写自己的BeanFactoryPostProcessor,则也应该考虑实现Ordered接口。 有关更多详细信息,请参见BeanFactoryPostProcessor和Ordered接口的javadoc。

如果要更改实际的bean实例(即,从配置元数据创建的对象),则需要使用BeanPostProcessor(在前面的使用BeanPostProcessor定制Bean中进行了介绍)。 从技术上讲,可以在BeanFactoryPostProcessor中使用Bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致过早的Bean实例化,从而违反了标准容器的生命周期。 这可能会导致负面影响,例如绕过bean后处理。

 

同样,BeanFactoryPostProcessor实例是按容器限定作用域的。 仅在使用容器层次结构时才有意义。 如果在一个容器中定义BeanFactoryPostProcessor,则仅将其应用于该容器中的Bean定义。 一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都属于同一层次结构也是如此。

当在ApplicationContext中声明bean工厂后处理器时,它会自动执行,以便对定义容器的配置元数据应用更改。Spring包括许多预定义的bean工厂后处理器,如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor - 来注册自定义属性编辑器。

ApplicationContext会自动检测部署到它中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候将这些bean用作bean工厂的后处理器。您可以像部署其他bean一样部署这些后处理器bean。

与BeanPostProcessors一样,您通常不想为延迟初始化配置BeanFactoryPostProcessors。 如果没有其他bean引用Bean(Factory)PostProcessor,则该后处理器将完全不会实例化。 因此,将其标记为延迟初始化将被忽略,即使您在<beans />元素的声明中将default-lazy-init属性设置为true,Bean(Factory)PostProcessor也会被实例化。

Example: 类名替换 PropertySourcesPlaceholderConfigurer

您可以使用PropertySourcesPlaceholderConfigurer通过使用标准Java属性格式将外部Bean定义的属性值外部化到单独的文件中。 这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库URL和密码,而无需为修改容器的主要XML定义文件而复杂或冒风险。

参考以下基于XML的配置元数据片段,其中定义了具有占位符值的DataSource:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

该示例显示了从外部属性文件配置的属性。 在运行时,将PropertySourcesPlaceholderConfigurer应用于替换数据源某些属性的元数据。 将要替换的值指定为$ {property-name}格式的占位符,该格式遵循Ant和log4j和JSP EL样式。

实际值来自标准Java属性格式的另一个文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

因此,$ {jdbc.username}字符串在运行时将被替换为值“ sa”,并且对于与属性文件中的键匹配的其他占位符值也是如此。 PropertySourcesPlaceholderConfigurer检查Bean定义的大多数属性和属性中的占位符。 此外,您可以自定义占位符前缀和后缀。

借助Spring 2.5中引入的上下文名称空间,您可以使用专用配置元素配置属性占位符。 您可以在location属性中提供一个或多个位置作为逗号分隔的列表,如以下示例所示:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertySourcesPlaceholderConfigurer不仅在您指定的属性文件中查找属性。 默认情况下,如果无法在指定的属性文件中找到属性,则会检查Spring Environment属性和常规Java System属性。

您可以使用PropertySourcesPlaceholderConfigurer替换类名称,这在您必须在运行时选择特定的实现类时有时很有用。 以下示例显示了如何执行此操作:

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果在运行时无法将类解析为有效的类,则在即将创建be an时(即在非延迟初始化 bean的ApplicationContext的preInstantiateSingletons()阶段)对该bean的解析将失败。

Example: PropertyOverrideConfigurer

另一个bean工厂后处理程序PropertyOverrideConfigurer与PropertySourcesPlaceholderConfigurer相似,但是与后者不同,原始定义可以具有默认值,也可以完全没有bean属性的值。 如果覆盖的 Properties 文件没有某个bean属性的条目,则使用默认的上下文定义。

注意,bean定义不知道会被覆盖,因此从XML定义文件中不能立即看出正在使用覆盖配置器。 如果有多个PropertyOverrideConfigurer实例为同一bean属性定义了不同的值,则由于覆盖机制,最后一个实例将获胜。

属性文件配置行采用以下格式:

beanName.property=value

下面的列表显示了格式示例:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

这个示例文件可以与容器定义一起使用,容器定义包含一个名为dataSource 的bean,该bean具有driver 和url 属性。

也支持复合属性名,只要路径的每个组件(被重写的最终属性除外)已经是非空的(可能由构造函数初始化)。在以下示例中,tom bean的fred属性的bob属性的sammy属性设置为标量值123:

tom.fred.bob.sammy=123

指定的重写值始终是文本值。它们不会转换为bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。

随着Spring2.5中引入了上下文命名空间,可以使用专用配置元素配置属性重写,如下例所示:

<context:property-override location="classpath:override.properties"/>

1.8.3.  使用 FactoryBean 自定义实例化逻辑

您可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。

FactoryBean接口是可插入Spring IoC容器的实例化逻辑的一点。 如果您有复杂的初始化代码,而不是(可能)冗长的XML,可以用Java更好地表达,则可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。

FactoryBean接口提供了三个方法:

  • Object getObject(): 返回此工厂创建的对象的实例。 实例可以共享,具体取决于该工厂是否返回单例或原型。

  • boolean isSingleton(): 如果此FactoryBean返回单例则返回true,否则返回false。

  • Class getObjectType(): 返回由getObject()方法返回的对象类型,如果类型未知则返回null。

FactoryBean的概念和接口在Spring框架中的许多地方使用。超过50个FactoryBean接口的实现与Spring本身一起提供。

当您需要向容器要求一个实际的FactoryBean实例本身而不是由它产生的bean时,请在调用ApplicationContext的getBean()方法时在该bean的ID前面加上&符号。 因此,对于给定的myBean id为FactoryBean的FactoryBean,在容器上调用getBean(" myBean")将返回FactoryBean的乘积,而调用getBean("&myBean")则将返回FactoryBean实例本身。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值