1.8 容器的扩展功能
通常,应用程序开发人员不需要继承ApplicationContext实现类的子类来实现IoC容器的扩展。相反,可以通过插入特殊集成接口的实现来扩展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实例后,在调用初始化方法(如InitializingBean.afterPropertiesSet()或init()方法)之前或之后使得BeanPostProcessor得到一个回调。BeanPostProcessor可以对bean实例执行任何操作,包括完全忽略回调。BeanPostProcessor通常检查回调接口,或者它可以用代理来包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被继承至BeanPostProcessor。
ApplicationContext自动检测配置元数据中实现BeanPostProcessor接口任何bean。并将这些bean作为BeanPostProcessor注册,以便以后在创建bean时可以调用它们。BeanPostProcessor可以像其他Bean一样部署在容器中。
需要注意的是当通过在配置类上使用@Bean工厂方法声明一个BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口类型,清楚的表明该类是BeanPostProcessor类。否则ApplicationContext不能根据类型自动检测到该bean。因为需要尽早实例化BeanPostProcessor以便应用于上下文中其他bean的初始化,所以这种早期类型检测非常关键。
- 以编程方式注册BeanPostProcessor实例
虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您还可以通过使用addBeanPostProcessor方法以编程方式在ConfigurableBeanFactory上注册它们。当您需要在注册之前评估条件逻辑时,这将非常有用。但是请注意,以编程方式添加的BeanPostProcessor实例并不尊重Ordered接口。在这里,登记的顺序决定了执行的顺序。还请注意,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前被处理,而不考虑任何显式排序。
下面的例子展示了如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例。
案例: BeanPostProcessor的Hello World编程
第一个示例演示了基本用法。该示例描述了一个自定义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;
}
}
复制代码
下面的xml中注册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动态语言支持在动态语言支持一章中有详细介绍。)
以下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
复制代码
将回调接口或注释与自定义BeanPostProcessor实现类结合使用是扩展Spring IoC容器的一种常见方法。一个例子是Spring的AutowiredAnnotationBeanPostProcessor,它处理@Autowired注释的属性和方法
1.8.2 使用BeanFactoryPostProcessor定制配置元数据
我们将要介绍的下一个扩展功能是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义类似于BeanPostProcessor的语义,但有一个主要的区别:BeanFactoryPostProcessor操作beans配置元数据。也就是说,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中声明BeanFactoryPostProcessor时容器将自动运行它,以便对容器的配置元数据进行更改。Spring包括许多预定义的BeanFactoryPostProcessor,比如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor—例如,注册自定义属性编辑器。
ApplicationContext自动检测部署任何实现BeanFactoryPostProcessor接口的bean。它在适当的时候使用这些bean作为BeanFactoryPostProcessor。您可以像部署任何其他bean一样部署这些后处理器bean。
与BeanPostProcessors一样,您通常不希望将BeanFactoryPostProcessors配置为懒加载。如果没有其他bean引用BeanFactoryPostProcessor,则该BeanFactoryPostProcessor根本不会被实例化。因此,将其标记为懒加载将被容器忽略,即使您在元素的声明中将default-lazy-init属性设置为true, Bean(Factory)PostProcessor也将被急切地实例化。
案例1:PropertySourcesPlaceholderConfigurer
您可以使用PropertySourcesPlaceholderConfigurer通过使用标准Java Properties格式将bean定义中的属性值外化到一个单独的文件中。这样做可以使部署应用程序的人员自定义特定于环境的属性,如数据库url和密码,而无需修改容器的主XML定义文件或其他文件的复杂性或风险。
考虑以下基于xml的配置元数据片段,其中定义了一个带有占位符值的数据源:
<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文件获取属性配置。在运行时,PropertySourcesPlaceholderConfigurer被用于替换数据源的一些属性的元数据。要替换的值用${property-name}占位符表示。实际值来自另一个标准Java Properties格式的文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
复制代码
$ {jdbc.username}字符串在运行时被替换为值’sa’。
使用Spring 2.5中引入的context元素,您可以使用专用的配置元素配置属性占位符。您可以在location属性中以逗号分隔的列表形式提供一个或多个配置文件地址,如下面的示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
复制代码
案例2:PropertyOverrideConfigurer
PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,但与后者不同的是,前者的属性定义可以有缺省值,或者根本没有bean属性的值。如果覆盖的Properties文件没有特定bean属性的条目,则使用默认上下文定义。要注意的是,bean定义不知道自身被覆盖,因此从XML定义文件中不能立即看出正在使用的PropertyOverrideConfigurer。如果多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,由于覆盖机制,最后一个实例设置的值会被最终采纳。
proerties文件配置行采用以下格式:
beanName.property=value
复制代码
示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
复制代码
这个示例文件可以要与一个容器定义一起使用,该容器定义包含一个名为dataSource的bean,该bean具有driverClassName和url属性。
在spring2.5中引入的context命名空间,使用如下示例配置PropertyOverrideConfigurer:
<context:property-override location="classpath:override.properties"/>
复制代码
1.8.3使用FactoryBean自定义实例化逻辑
为工厂类实现org.springframework.factory.factorybean接口。
我们可通过FactoryBean接口插入Spring IoC容器对象实例化逻辑。如果您有复杂的初始化代码,使用Java代码可更好地表达,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义的FactoryBean注册到容器中。
FactoryBean接口提供了三个方法:
(T getObject(): 返回此工厂创建的对象的一个实例。该实例是否被共享取决于工厂返回的是singletons还是prototypes。
boolean isSingleton(): 如果FactoryBean返回单例,则返回true,否则返回false。该方法的默认实现返回true。
Class<?> getObjectType(): 返回getObject()方法返回的对象的类型,如果事先不知道该类型,则返回null。
FactoryBean概念和接口在Spring框架的许多地方都被使用。Spring本身附带了超过50个FactoryBean接口的实现。当您需要向容器请求一个实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前面加上&符号。因此,对于一个id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的工厂方法返回的对象,而调用getBean(“&myBean”)将返回FactoryBean实例本身。