Spring官方文档 1.8 Container Extension Points(Spring容器提供的扩展点)

Container Extension Points(Spring容器提供的扩展点)

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

1.8.1. Customizing Beans by Using a BeanPostProcessor

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 definition(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数据中所述。

该org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。当此类被注册为容器的后处理器时,对于容器创建的每个bean实例,后处理器拥有两次回调方法机会,首先每次都会在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何声明的init方法)被使用之前从容器获得回调,第二是或在任何bean初始化回调之后调用。后处理器可以对bean实例执行任何操作,包括完全忽略回调。Bean后处理器通常检查回调接口,或者可以用代理包装Bean。一些Spring AOP基础结构类被实现为bean后处理器,以提供代理包装逻辑。

The org.springframework.beans.factory.config.BeanPostProcessor interface consists of exactly two callback methods. When such a class is registered as a post-processor with the container, for each bean instance that is created by the container, the post-processor gets a callback from the container both before container initialization methods (such as InitializingBean.afterPropertiesSet() or any declared init method) are called, and after any bean initialization callbacks.

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

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

Programmatically registering BeanPostProcessor instances(以编程方式注册BeanPostProcessor实例)

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

BeanPostProcessor 实例和AOP自动代理

实现BeanPostProcessor接口的类是特殊的,并且容器对它们的处理方式有所不同。所有BeanPostProcessor实例以及它们直接引用的bean在启动时都会实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,BeanPostProcessor以排序方式注册所有实例,并将其应用于容器中的所有其他bean。因为AOP自动代理是BeanPostProcessor实现的,所以BeanPostProcessor 实例或它们直接引用的bean都没有资格进行自动代理,因此,没有编织的aspects。

对于任何此类bean,您应该看到一条参考性日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)。

即:someBean不适合被所有BeanPostProcessor接口处理(例如:不适合自动代理)

如果您使用自动装配或@Resource(可能会自动装配)将Bean连接到您的 BeanPostProcessor,则Spring在搜索类型匹配的依赖项候选对象时可能会访问意外的beans,因此使它们不适合进行自动代理或其他类型的bean post-processing。例如,如果您有一个@Resource注释的依赖项,其中属性不直接与bean的声明名称相对应,并且不使用name属性,Spring将通过按类型匹配访问其他bean。

以下示例显示了如何在中编写,注册和使用BeanPostProcessor实例ApplicationContext。

Example: Hello World, BeanPostProcessor-style

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

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

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

以下beans元素使用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应用程序运行上述代码和配置:

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

}

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

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

Example: The RequiredAnnotationBeanPostProcessor
将回调接口或注解与自定义BeanPostProcessor实现结合使用 是扩展Spring IoC容器的常用方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor -一个 Spring发行版附带的BeanPostProcessor实现,它确保标记了注解的bean上的JavaBean属性在实际上依赖注入一个值。

1.8.2. Customizing Configuration Metadata with a BeanFactoryPostProcessor(使用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 (正如前面描述的自定义Bean中所用的BeanPostProcessor)。尽管在技术上可以使用BeanFactoryPostProcessor(例如,通过使用 BeanFactory.getBean())中的bean实例,但是这样做会导致bean实例化过早,从而违反了标准容器生命周期。这可能会导致负面影响,例如绕过bean后处理。
而且,BeanFactoryPostProcessor实例是按容器划分作用域的。仅在使用容器层次结构时才有意义。如果你在一个容器中定义一个BeanFactoryPostProcessor,则仅能将其应用于该容器中的bean definition。一个容器中的Bean definition不会被另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都属于同一层次结构也是如此。

当Bean工厂后处理器在ApplicationContext内申明时,它将自动运行 ,以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和 PropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor -例如,去注册自定义属性编辑器。

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

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

Example: The Class Name Substitution PropertySourcesPlaceholderConfigurer(类名替换 PropertySourcesPlaceholderConfigurer)

您可以使用PropertySourcesPlaceholderConfigurer来在单独的文件中外部化Bean definition的属性值通过标准Java Properties格式。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库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>

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

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

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

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

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

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

在PropertySourcesPlaceholderConfigurer不仅将查找在属性Properties 指定的文件。默认情况下,如果无法在指定的属性文件中找到属性,则会检查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}"/>

如果无法在运行时将该类解析为有效的类,则在将要创建该bean时(在非懒加载bean 的preInstantiateSingletons() 阶段)解析将失败ApplicationContext。

If the class cannot be resolved at runtime to a valid class, resolution of the bean fails when it is about to be created, which is during the preInstantiateSingletons() phase of an ApplicationContext for a non-lazy-init bean.

Example: The PropertyOverrideConfigurer

在PropertyOverrideConfigurer之后的另一个bean工厂后处理器,类似 PropertySourcesPlaceholderConfigurer,但不同像后者,原来的bean definition的属性可以只有默认值或者根本没有值。如果一个重写Properties文件中没有某个bean属性的条目,则使用默认的上下文定义

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

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

beanName.property=value

下面的清单显示了格式的示例:

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

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

只要路径的每个组成部分(最终属性被覆盖)之外的所有组成部分都已经为非空(可能由构造函数初始化),则也支持复合属性名。在下面的示例中,sammy设置为值123:

tom.fred.bob.sammy=123
指定的用来重写的值通常是文字值。它们不会转换为bean引用。当XML bean definition中的原始值指定bean引用时,此约定也适用。

用contextSpring 2.5中引入的名称空间,可以使用专用配置元素配置属性覆盖,如以下示例所示:

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

1.8.3. Customizing Instantiation Logic with a FactoryBean(自定义实例化逻辑)

您可以实现org.springframework.beans.factory.FactoryBean接口那些本身就是自己工厂的对象。
You can implement the org.springframework.beans.factory.FactoryBean interface for objects that are themselves factories.
FactoryBean接口是可插入Spring IoC容器的实例化逻辑的一点。如果您有复杂的初始化代码,而不是(可能)冗长的XML量,可以用Java更好地表达,则可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。

FactoryBean interface 提供三个方法:

  1. Object getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于该工厂是否返回单例或prototypes。
  2. boolean isSingleton():如果FactoryBean是例返回true单或其他则返回 false。
  3. Class getObjectType():返回getObject()方法返回的对象类型,或者如果类型未知,则返回null。

Spring框架中的许多地方都使用了FactoryBean概念和接口。FactoryBeanSpring本身附带了50多种接口实现。

当您需要向容器询问FactoryBean本身而不是由它产生的bean的实际实例时,请在调用的方法时在该bean的id前面加上“&”符号(&)。因此,对于给定 id的Bean Fatory,调用 getBean(“myBean”)将返回该Bean Factory在容器中生成的产品,若调用而调用 getBean("&myBean"),则返回的实例本身。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值