4.8 容器扩展点

通常情况下,应用程序开发人员不需要编写 ApplicationContext 实现类的子类。 相反,Spring 的 IoC 容器可以通过插件的方式来扩展,就是实现特定的整合接口。下面的几 个章节会来说明这些整合接口。

4.8.1 使用 BeanPostProcessor 来自定义 bean

BeanPostProcessor 接口定义了你可以提供实现你自己的(或覆盖容器默认的)实 例逻辑,依赖解析逻辑等的回调方法。如果你想在 Spring 容器完成实例化,配置和初始化 bean 之后实现一些自定义逻辑,那么你可以使用一个或多个 BeanPostProcessor 实现类 的插件。

你可以配置多个 BeanPostProcessor 实例,而且你还可以通过设置 order 属性来 控制这些 BeanPostProcessor 执行的顺序。 仅当 BeanPostProcessor 实现了 Ordered 接口你才可以设置设个属性;如果你想编写你自己的 BeanPostProcessor,你 也应该考虑实现 Ordered 接 口 。 要 了 解 更 多 细 节 , 可 以 参 考 JavaDoc 文档中的 BeanPostProcessor 和 Ordered 接口。

注意

BeanPostProcessor 操作 bean(对象)实例;那也就是说,Spring 的 IoC 容器 实例化 bean 的实例,之后 BeanPostProcessor 来做它们要做的事情。

BeanPostProcessor 的范围是对于每一个容器来说的。如果你使用了容器继承,那么这才有所关联。如果你在一个容器中定义了 BeanPostProcessor,那么它仅仅 会在那个容器中后处理 bean。换句话说,一个容器中定义的 bean 不会被另外一个容器 中定义的 BeanPostProcessor 来进行后处理,即便是两个容器都是相同继承链上的 一部分。

要修改真正的 bean 定义(也就是说,定义 bean 的蓝图),你可以使用 4.8.2 节,“使 用 BeanFactoryPostProcessor 来 自 定 义 配 置 元 数 据 ” 描 述 的 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 那样。

BeanPostProcessor 和 AOP 自动代理

实现了 BeanPostProcessor 接口的类是特殊的,会被容器不同对待。所有它们参照的 BeanPostProcessor 和 bea n 会在启动时被实例化,作为

ApplicationContext 启 动 阶 段 特殊 的 一 部 分 。 接 下 来 , 所 有 的 BeanPostProcessor 以排序的方式注册并应用于容器中的其它 bean。因为 AOP 自动 代理作为 BeanPostProcessor 本身的实现,它们为自动代理资格的直接引用的既不是 BeanPostProcessor 也不是 bean,因此没有织入它们的方面。

对于这样的 bean,你应该看到一个信息级的日志消息:“Bean foo 没有由所有BeanPostProcessor 接口处理的资格(比如:没有自动代理的资格)”。 下面的示例展示了如何在 ApplicationContext 中编写,注册和使用BeanPostProcessor。

4.8.1.1 示例:BeanPostProcessor 风格的 Hello World

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

自定义 BeanPostProcessor 实现类的定义:

package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor; 
import org.springframework.beans.BeansException;
public class InstantiationTracingBeanPostProcessor implements
BeanPostProcessor {
    // 简单地返回实例化的bean
    public Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {
        return bean; // 这里我们可能返回任意对象的引用...
    }
    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-3.0.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd">
    <lang:groovy id="messenger"
        script-source="classpath:org/springframework/scripting/gr oovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So
        Dreamy."/>
    </lang:groovy>
    <!--
    当上面的bean(messenger)被实例化时,这个自定义的BeanPostProcessor实 现会输出实际内容到系统控制台
    -->
    <bean
    class="scripting.InstantiationTracingBeanPostProcessor" />
</beans>

注意 InstantiationTracingBeanPostProcessor 仅仅是简单地定义。它也没有 命名,因为它会像其它 bean 那样被依赖注入。(前面的配置也可以定义成 Groovy 脚本支持 的 bean。Spring 2.0 动态语言支持在第 27 章中来详细说明)

下面示例的 Java 应用程序执行了前面配置的代码:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationConte xt;
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

4.8.1.2 示例:RequiredAnnotationBeanPostProcessor

配合自定义的 BeanPostProcessor 实现使用回调接口或者注解,是扩展 Spring IoC 容器的一种通用方式。Spring 的 RequiredAnnotationBeanPostProcessor 就是一个 例子 – 一种 BeanPostProcessor 实现,随着 Spring 一起发布,来保证 bean 中被(任意) 注解所标记的 JavaBean 属性在真正(配置)注入时有值。

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

下 一 个 扩 展 点 我 们 要 来 看 看 org.springframework.beans.factory.config.BeanFactoryPostProcessor 。 这个接口的 语义和那 些 BeanPostProcessor 是相 似的, 但有一个 主要的不 同点: BeanFactoryPostProcessor 操作 bean 的配置元数据;也就是说 Spring 的 IoC 容器允许 BeanFactoryPostProcessor 来 读 取 配 置 元 数 据 并 在 容 器 实 例 化 BeanFactoryPostProcessor 以外的任何 bean 之前可以修改它。

你可以配置多个 BeanFactoryPostProcessor,并且你也可以通过 order 属性来控 制 这 些 BeanFactoryPostProcessor 执 行 的 顺 序 。 然 而 , 仅 当 BeanFactoryPostProcessor 实现 Ordered 接口时你才能设置这个属性。如果编写你自己的 BeanFactoryPostProcessor,你也应该考虑实现 Ordered 接口。参考 JavaDoc 文档来获取 BeanFactoryPostProcessor 和 Ordered 接口的更多细节。

注意

如果你想改变真实的 bean 实例(也就是说,从配置元数据中创建的对象),那么你 需要使用 BeanPostProcessor(在上面 4.8.1 节,“使用 BeanPostProcessor 来自定义 bean ”中描述)来代替。在 BeanFactoryPostProcessor (比如使用 BeanFactory.getBean())中来使用这些 bea n 的实例虽然在技术上是可行的,但 这么来做会引起 bean 过早实例化,违反标准的容器生命周期。这也会引发一些副作用, 比如绕过 bean 的后处理。

而且,BeanFactoryPostProcessor 的范围也是对每一个容器来说的。如果你 使 用 了容 器的 继承 的话 ,这 就是 唯一 相关 的点 了。 如果 你在 一个 容器 中定 义 了 BeanFactoryPostProcessor,那么它只会用于在那个容器中的 bean。一个容器中 Bean 的定义不会被另外一个容器中的 BeanFactoryPostProcessor 后处理,即便 两个容器都是相同继承关系的一部分。

当在 ApplicationContext 中声明时,bean 工厂后处理器会自动被执行,这就可以 对定义在容器中的配置元数据进行修改。Spring 包含了一些预定义的 bean 工厂后处理器,比如 PropertyOverrideConfigurer 和 PropertyPlaceholderConfigurer.。自定义的 BeanFactoryPostProcessor 也可以来用,比如,注册自定义的属性编辑器。

ApplicationContext 会 自 动 检 测 任 意 部 署 其 中 , 且 实 现 了 BeanFactoryPostProcessor 接口的 bean。在适当的时间,它用这些 bean 作为 bean 工厂后处理器。你可以部署这些后处理器 bean 作为你想用的任意其它的 bean。

注意

和 BeanPostProcessor 一样,通常你不会想配置 BeanFactoryPostProcessor 来进行延迟初始化。如果没有其它 bean 引用 Bean(Factory)PostProcessor,那么后 处理器就不会被初始化了。因此,标记它为延迟初始化就会被忽略,即便你在<beans/>元 素声明中设置 default-lazy-init 属性为 true,那么 Bean(Factory)PostProcessor 也会正常被初始化。

4.8.2.1 示例:PropertyPlaceholderConfigurer

你可以使用来对使用了标准 Java Properties 格式的分离文件中定义的 bean 来声明属 性值。这么来做可以使得部署应用程序来自定义指定的环境属性,比如数据库的连接 URL 和密码,不会有修改容器的主 XML 定义文件或其它文件的复杂性和风险。

考虑一下下面这个基于 XML 的配置元数据代码片段,这里的 dataSource 就使用了占位符来定义。这个示例展示了从 Properties 文 件 中 配 置 属 性 的 方 法 。 在 运 行 时 , PropertyPlaceholderConfigurer 就会用于元数据并为数据源替换一些属性。指定替 换的值作为${属性-名}形式中的占位符,这里应用了 Ant/log4j/JSP EL 的风格。

<bean class="org.springframework.beans.factory.config.PropertyPlaceho lderConfigurer">
    <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"/>

PropertyPlaceholderConfigurer 不仅仅查看在 Properties 文件中指定的属 性。默认情况下,如果它不能在指定的属性文件中发现属性,它也会检查 Java System 属性。 你可以通过设置 systemPropertiesMode 属性,使用下面整数的三者之一来自定义这种 行为:

never(0):从不检查系统属性

fallback(1):如果没有在指定的属性文件中解析到属性,那么就检查系统属性。这是默 认的情况。

override(2):在检查指定的属性文件之前,首先去检查系统属性。这就允许系统属性覆 盖其它任意的属性资源。

查看 PropertyPlaceholderConfigurer 的 JavaDoc 文档来获取更多信息。

类名替换

你可以使用 PropertyPlaceholderConfigurer 来替换类名,在运行时,当你不得不去选择一个特定的实现类时,这是很有用的。比如:

<bean class="org.springframework.beans.factory.config.PropertyPlac eholderConfigurer">
    <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()阶段发生的。

4.8.2.2 示例:PropertyOverrideConfigurer

PropertyOverrideConfigurer , 另 外 一 种 bean 工 厂 后 处 理 器 , 类 似 于 PropertyPlaceholderConfigurer,但不像后者,对于所有 bean 的属性,原始定义可 以有默认值或没有值。如果一个 Properties 覆盖文件没有特定 bean 的属性配置项,那么 就会使用默认的上下文定义。

注意,bean 定义是不知道被覆盖的,所以从 XML 定义文件中不能立即明显反应覆盖配 置。在多个 PropertyOverrideConfigurer 实例的情况下,为相同 bean 的属性定义不同的值,那么最后一个有效,这就是覆盖机制。 属性文件配置行像这种格式:

beanName.property=value

比如:

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

这个示例文件可以用于包含了 dataSource 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"/>

4.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 Framework 中的很多地方;随 Spring 发行, 有超过 50 个 FactoryBean 接口的实现类。

当你需要向容器请求一个真实的 FactoryBean 实例,而不是它生产的 bean,当调用 ApplicationContext 的 getBean()方法时,在 bean 的 id 之前要有连字符(&)。所以 对于一个给定 id 为 myBean 的 FactoryBean,调用容器的 getBean("myBean")方法返回的 FactoryBean 产品;而调用 getBean("&myBean")方法则返回 FactoryBean 实例本身。