IoC容器8——容器扩展点

容器扩展点

通常的,一个应用开发者不须要扩展ApplicationContext的实现类。相反,Spring IoC容器能够经过插入特殊的集成接口的实现被扩展。mysql

1 使用 BeanPostProcessor 自定义bean

BeanPostProcessor接口定义了一些回调函数,能够经过实现它们提供本身的(覆盖容器默认的)实例化逻辑、依赖关系解析逻辑等等。若是想在Spring容器完成实例化、配置和初始化bean后实现一些用户逻辑,能够插入一个或多个BeanPostProcessor实现。spring

能够配置多个BeanPostProcessor实例,而且能够经过设置order属性来控制BeanPostProcessor的执行顺序。只有BeanPostProcessor实现了Ordered接口才能设置这个属性;若是本身编写BeanPostProcessor应该考虑实现Ordered接口。sql

BeanPostProcessor操做bean或者对象的实例,所以Spring IoC容器实例化一个bean的实例而后BeanPostProcessor开始工做。数据库

BeanPostProcessor的做用域是每一个容器一个。这与使用容器层次结构有关。若是在一个容器中定义了一个BeanPostProcessor,它仅仅“后处理”这个容器中的bean。也就是说,在一个容器中定义的bean不会被另外一个容器定义的BeanPostProcessor后处理,即便两个容器是同一层次结构的一部分。apache

为了改变实际的bean定义(即定义bean的蓝图),须要使用BeanFactoryPostProcessor。编程

org.springframework.beans.factory.config.BeanPostProcessor接口包括两个回调方法。当这样的类被注册为容器的后处理器,对于容器建立的每个bean实例,后处理器从容器获取回调,在容器调用初始化方法(例如InitializingBean的afterPropertiesSet()以及任何声明初始化方法)以前以及任何bean的初始化回调以后。后处理器能够对bean实例进行任何操做,包括彻底忽略回调(???)。一个bean后处理器一般检查回调接口或者用一个代理包裹一个bean。一些Spring AOP基础设置类是bean后处理器的实现,为了提供代理包裹逻辑。编辑器

ApplicationContext自动探测在配置元数据中定义的实现了BeanPostProcessor接口的bean。ApplicationContext注册这些bean为后处理器,这样它们会在bean建立后被调用。Bean后处理器能够像其它bean同样在容器中发布。ide

注意,当在配置类中使用@Bean工厂方法声明一个BeanPostProcessor,工厂方法的返回类型应该是实现类自身或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,以明确的代表bean的后处理器属性。不然ApplicationContext在彻底建立后不一样经过类型自动发现它。因为一个BeanPostProcessor须要在上下文中的其它bean实例化以前被实例化,因此这种预先类型检测机制是必须的。函数

尽管推荐的BeanPostProcessor的注册方法是经过ApplicationContext的自动探测机制(上文所述),可是也可使用ConfigurableBeanFactory的addBeanPostProcessor方法编程的注册它们。这在须要注册以前执行条件逻辑或者在层次结构中将bean后处理器拷贝到其它上下文时有用。须要注意的是经过编程方法添加BeanPostProcessor不遵照Ordered接口。这里注册的顺序意味着执行的顺序。此外编程方法注册的后处理器老是在自动检测方法注册的后处理器以前执行,无论任何顺序。post

实现了BeanPostProcessor接口的类是特殊的而且被容器不一样对待。全部的BeanPostProcessor和它们的直接依赖在启动时实例化,做为ApplicationContext特殊启动阶段的一部分。而后,全部BeanPostProcessor以排序的方式注册而且应用于容器中的全部其它bean。由于AOP自动代理自身也实现了BeanPostProcessor,因此BeanPostProcessor和它的直接依赖都不能被自动代理,所以不要对它们编制面。

对任何这样的bean(使用了自动代理),能够看到消息日志:“Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)”。

注意,若是bean使用自动装配或者@Resource(可能回退到自动装配)注入到BeanPostProcessor,当搜索类型匹配的依赖候选者时,Spring也许访问了不指望的bean,所以使得它们不能被自动代理者或者使得它们成为其它类型的bean后处理器。例如,若是使用@Resource注释的依赖项,其中字段/ setter名称与bean的已声明名称不直接对应而且没有使用name属性,那么Spring将访问其余bean以便按类型匹配它们。

下面的例子展现如何在ApplicationContext中编写、注册和使用BeanPostProcessor。

例子:Hello World,BeanPostProcessor风格

第一个例子展现基础用法。例子展现一个用户BeanPostProcessor实现,它当容器建立每个bean的时候调用bean的toString()方法而且把结果字符串打印到系统控制台。

package scripting;

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

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://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。

下面的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 = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

应用的输出相似下面的形式:

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

例子:RequiredAnnotationBeanPostProcessor

使用回调接口或者注解关联用户BeanPostProcessor实现是一种扩展Spring IoC容器的常见方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor——Spring distribution附带的BeanPostProcessor实现,它保证使用注解注释的JavaBean的属性确实依赖注入了值。

2 使用BeanFactoryPostProcessor自定义配置元数据

org.springframework.beans.factory.config.BeanFactoryPostProcessor是另外一个扩展点。这个接口的语义与BeanPostProcessor相似,但有一点主要的不一样:BeanFactoryPostProcessor操做bean配置元数据;即Spring IoC容器容许BeanFactoryPostProcessor读取配置元数据而且在容器实例化除BeanFactoryPostProcessor以外的任何bean以前改变它。

能够配置多个BeanFactoryPostProcessor,能够控制BeanFactoryPostProcessor的执行顺序,经过设置order属性。然而只有当BeanFactoryPostProcessor实现了Ordered接口才能设置order属性。若是本身实现BeanFactoryPostProcessor,建议实现Ordered接口。

若是想要改变实际的bean实例(即根据配置元数据建立的对象),须要使用BeanPostProcessor。尽管技术上使用BeanFactoryPostProcessor来修改bean实例(例如使用BeanFactory.getBean()),这么作致使bean过早的实例化,违反了标准的容器生命周期。这可能会致使负做用,如bypassing bean 后处理(???)。

BeanFactoryPostProcessor的做用域是每一个容器一个。这与使用容器层次结构有关。若是在一个容器中定义了一个BeanFactoryPostProcessor,它仅仅应用于这个容器中的bean定义。也就是说,在一个容器中中的bean定义不会被另外一个容器定义的BeanFactoryPostProcessor后处理,即便两个容器是同一层次结构的一部分。

当一个bean工厂后处理器在ApplicationContext中声明,它会被自动执行,以便对容器中的配置元数据定义进行修改。Spring包含了一些预约义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。也可使用一个用户定义的BeanFactoryPostProcessor,例如注册自定义属性编辑器(???)。

ApplicationContext自动检测发布在其中的实现BeanFactoryPostProcessor接口的bean。它在适当的时候把这些bean做为bean工厂后处理器使用。能够像其它bean同样发布这些后处理器bean。

与BeanPostProcessor同样,一般不须要将BeanFactoryPostProcessor配置为懒加载。若是是懒加载,若是没有其它bean引用一个Bean(Factory)PostProcessor,后处理器将不会被实例化。所以,将其标注懒加载将会被忽略,而且Bean(Factory)PostProcessor会被当即实例化,甚至在<bean/>元素中将default-lazy-init属性设置为true。

例子:类名替换 PropertyPlaceholderConfigurer

可使用PropertyPlaceholderConfigurer能够将bean定义的属性外部化到单独的使用标准Java属性格式文件中。这么作能够在发布应用时自定义面向环境的属性例如数据库URL、密码,从而避免修改主XML定义文件或者容器文件的复杂性和风险。

考虑基于XML的配置元数据段,定义了使用占位符值的DataSource。下面的例子展现了在外部Properties文件定义属性值。在运行时,PropertyPlaceholderConfigurer被应用于元数据,替代Datasource的一些属性。被替代的值被指定为${property-name}形式、遵循Ant/log4j/JSP EL风格的占位符。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/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>

实际的值在另外一个文件中,使用标准Java Properties格式:

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

所以,字符串${jdbc.username}在运行时被替换为'sa',而且一样适用于与属性文件中的键匹配的其它占位符值。PropertyPlaceholderConfigurer检查bean定义中的大多数属性的占位符。此外,占位符的前缀和后缀都可自定义。

使用Spring 2.5引入的context命名空间,可使用专用的配置元素配置属性占位符。在location属性中,一个或多个定位可使用逗号分隔的列表提供。

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

PropertyPlaceholderConfiguerer不只仅在指定的Properties文件中查找属性。默认的,若是没有在指定的配置文件中找到,它也检查Java系统属性。能够自定义这种行为,经过设置systemPropertiesMode属性为如下支持的整数值:

  • never(0): 从不检查系统属性;
  • fallback(1): 若是在指定的配置文件中没法解析属性,则查找系统属性,这是默认的行为;
  • override(2): 首先检查系统属性,在试图解析指定的配置文件以前。这种行为运行系统属性覆盖其它属性源。

可使用PropertyPlaceholderConfigurer替换类名,当须要在运行时选择一个特定的实现类时这颇有用。例如:

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

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

若是没法在运行时解析为有效的类,那么在建立bean时会解析失败,这是在ApplicationContext非延迟初始化Bean的preInstantiateSingletons()阶段期间。

例子: PropertyOverrideConfigurer

PropertyOverrideConfigurer是另外一个bean工厂后处理器,相似PropertyPlaceholderConfigurer,可是与之不一样的是,原始的bean定义可使用默认值或者不使用值。若是覆盖的配置文件没有一个bean属性的键,将使用默认上下文定义。

请注意,bean定义不知道被覆盖,因此从XML定义文件不能判断覆盖配置程序正在被使用。在多个PropertyOverrideConfigurer实例为同一个bean属性定义不一样的值的状况,因为覆盖机制会使用最后一个。

配置文件定义行使用下面的格式:

beanName.property=value

例如:

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

这个示例文件能够被用于一个包含数据源bean的容器定义中,数据源包含driver和url属性。

也支持复合属性名,只要出了最终被覆盖的属性以外的每一个组件都是非空的(通常由构造函数实例化)。在下面的例子中:

foo.fred.bob.sammy=123

foo bean的fred属性的bob属性的sammy属性被设置为纯量123.

指定的覆盖值老是字面值,它们不能被翻译成bean引用。当XML中的bean定义的原始值指定了 bean 引用时,这个约定也适用。

使用Spring 2.5引入的context命名空间,可使用专用的配置元素配置属性覆盖。

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

3 使用FactoryBean自定义实例化逻辑

实现org.springframework.beans.factory.FactoryBean接口的对象自身就是工厂类。

FactoryBean接口是Spring IoC容器的实例化逻辑的可插入点。若是有复杂的实例化代码,用Java代码能够更好的表达,而不是(可能)冗长的XML,能够建立本身的FactoryBean,在类中编写复杂的实例化逻辑,而后将自定义的FactoryBean插入容器中。

FactoryBean接口提供三个方法:

  • Object getObject():返回工厂建立的一个对象实例。实例可能被共享,取决于工厂返回singletons仍是prototypes。
  • boolean isSingleton():若是FactoryBean返回singleton,则返回true,不然返回false。
  • Class getObjectType():返回对象的类型,若是实现不知道类型则返回null。

FactoryBean的概念和接口在Spring Framework中的许多地方都被用到;Spring自身有超过50个FactoryBean接口的实现。

若是须要向容器获取一个实际的FactoryBean实例自身替代它生产的bean,在调用ApplicationContext的getBean()方法时,在bean的id以前加&符号。因此,对于给定的FactoryBean,id为myBean,调用容器的getBean("myBean")返回FactoryBean生产的对象,调用getBean("&myBean")返回FactoryBean实例自己。

相关文章
相关标签/搜索