Spring框架的IoC容器被设计为可扩展的。通常我们并不需要子类化各个BeanFactory
或ApplicationContext
实现类。而通过plugin各种集成接口实现来进行扩展。下面几节专门描述这些不同的集成接口。
我们关注的第一个扩展点是BeanPostProcessor
接口。它定义了几个回调方法,实现该接口可提供自定义(或默认地来覆盖容器)的实例化逻辑、依赖解析逻辑等。如果你想在Spring容器完成bean的实例化、配置和其它的初始化后执行一些自定义逻辑,你可以插入一个或多个的BeanPostProcessor
实现。
如果配置了多个BeanPostProcessor
,那么可以通过设置'order'
属性来控制BeanPostProcessor
的执行次序(仅当BeanPostProcessor
实现了Ordered
接口时,你才可以设置此属性,因此在编写自己的BeanPostProcessor
实现时,就得考虑是否需要实现Ordered
接口);请参考BeanPostProcessor
和Ordered
接口的JavaDoc以获取更详细的信息。
注意
BeanPostProcessor
可以对bean(或对象)的多个实例进行操作;也就是说,Spring IoC容器会为你实例化bean,然后BeanPostProcessor
去处理它。
如果你想修改实际的bean定义,则会用到BeanFactoryPostProcessor
(详情见第 3.7.2 节 “用BeanFactoryPostProcessor
定制配置元数据”)。
BeanPostProcessor
的作用域是容器级的,它只和所在容器有关。如果你在容器中定义了BeanPostProcessor
,它仅仅对此容器中的bean进行后置处理。BeanPostProcessor
将不会对定义在另一个容器中的bean进行后置处理,即使这两个容器都处在同一层次上。
org.springframework.beans.factory.config.BeanPostProcessor
接口有两个回调方法可供使用。当一个该接口的实现类被注册(如何使这个注册生效请见下文)为容器的后置处理器(post-processor)后,对于由此容器所创建的每个bean实例在初始化方法(如afterPropertiesSet和任意已声明的init方法)调用前,后置处理器都会从容器中分别获取一个回调。后置处理器可以随意对这个bean实例执行它所期望的动作,包括完全忽略此回调。一个bean后置处理器通常用来检查标志接口,或者做一些诸如将一个bean包装成一个proxy的事情;一些Spring AOP的底层处理也是通过实现bean后置处理器来执行代理包装逻辑。
重要的一点是,BeanFactory
和ApplicationContext
对待bean后置处理器稍有不同。ApplicationContext
会自动检测在配置文件中实现了BeanPostProcessor
接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它。部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory
实现的时候,bean 后置处理器必须通过下面类似的代码显式地去注册:
ConfigurableBeanFactory factory = new XmlBeanFactory(...);
// now register any needed BeanPostProcessor
instances
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);
// now start using the factory
因为显式注册的步骤不是很方便,这也是为什么在各种Spring应用中首选ApplicationContext
的一个原因,特别是在使用BeanPostProcessor
时。
注意
请不要将BeanPostProcessor
标记为延迟初始化。如果你这样做,Spring容器将不会注册它们,自定义逻辑无法得到应用。假如你在<beans/>
元素的定义中使用了'default-lazy-init'
属性,请确信你的各个BeanPostProcessor
标记为'lazy-init="false"'
关于如何在ApplicationContext
中编写、注册并使用BeanPostProcessor
,会在接下的例子中演示。
第一个实例似乎不太吸引人,但是它适合用来阐述BeanPostProcessor
的基本用法。我们所有的工作是编写一个BeanPostProcessor
的实现,它仅仅在容器创建每个bean时调用bean的toString()
方法并且将结果打印到系统控制台。它是没有很大的用处,但是可以让我们对BeanPostProcessor
有一个基本概念。
下面是BeanPostProcessor
具体实现类的定义:
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配置:
<?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-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.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,Spring2.0动态语言支持的细节请见第 24 章 动态语言支持)。
下面是测试代码:
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
在Spring的BeanPostProcessor
实现中调用标志接口或使用注解是扩展Spring IoC容器的常用方法。对于注解的用法详见第 25.3.1 节 “@Required
”,这里没有做深入的说明。通过定制BeanPostProcessor
实现,可以使用注解来指定各种JavaBean属性值并在发布的时候被注入相应的bean中。
我们将看到的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor
。这个接口跟BeanPostProcessor
类似,BeanFactoryPostProcessor
可以对bean的定义(配置元数据)进行处理。也就是说,Spring IoC容器允许BeanFactoryPostProcessor
在容器实际实例化任何其它的bean之前读取配置元数据,并有可能修改它。
如果你愿意,你可以配置多个BeanFactoryPostProcessor
。你还能通过设置'order'
属性来控制BeanFactoryPostProcessor
的执行次序(仅当BeanFactoryPostProcessor
实现了Ordered
接口时你才可以设置此属性,因此在实现BeanFactoryPostProcessor
时,就应当考虑实现Ordered
接口);请参考BeanFactoryPostProcessor
和Ordered
接口的JavaDoc以获取更详细的信息。
注意
如果你想改变实际的bean实例(例如从配置元数据创建的对象),那么你最好使用BeanPostProcessor
(见上面第 3.7.1 节 “用BeanPostProcessor
定制bean”中的描述)
同样地,BeanFactoryPostProcessor
的作用域范围是容器级的。它只和你所使用的容器有关。如果你在容器中定义一个BeanFactoryPostProcessor
,它仅仅对此容器中的bean进行后置处理。BeanFactoryPostProcessor
不会对定义在另一个容器中的bean进行后置处理,即使这两个容器都是在同一层次上。
bean工厂后置处理器可以手工(如果是BeanFactory
)或自动(如果是ApplicationContext
)地施加某些变化给定义在容器中的配置元数据。Spring自带了许多bean工厂后置处理器,比如下面将提到的PropertyResourceConfigurer
和PropertyPlaceholderConfigurer
以及BeanNameAutoProxyCreator
,它们用于对bean进行事务性包装或者使用其他的proxy进行包装。BeanFactoryPostProcessor
也能被用来添加自定义属性编辑器。
在一个BeanFactory
中,应用BeanFactoryPostProcessor
的过程是手工的,如下所示:
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties
file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
因为显式注册的步骤不是很方便,这也是为什么在不同的Spring应用中首选ApplicationContext
的原因,特别是在使用BeanFactoryPostProcessor
时。
ApplicationContext
会检测部署在它之上实现了BeanFactoryPostProcessor
接口的bean,并在适当的时候会自动调用bean工厂后置处理器。部署一个后置处理器同部属其他的bean并没有什么区别。
注意
正如BeanPostProcessor
的情况一样,请不要将BeanFactoryPostProcessors
标记为延迟加载。如果你这样做,Spring容器将不会注册它们,自定义逻辑就无法实现。如果你在<beans/>
元素的定义中使用了'default-lazy-init'
属性,请确信你的各个BeanFactoryPostProcessor
标记为'lazy-init="false"'
。
PropertyPlaceholderConfigurer
是个bean工厂后置处理器的实现,可以将BeanFactory
定义中的一些属性值放到另一个单独的标准Java Properties
文件中。这就允许用户在部署应用时只需要在属性文件中对一些关键属性(例如数据库URL,用户名和密码)进行修改,而不用对主XML定义文件或容器所用文件进行复杂和危险的修改。
考虑下面的XML配置元数据定义,它用占位符定义了DataSource
。我们在外部的Properties
文件中配置一些相关的属性。在运行时,我们为元数据提供一个PropertyPlaceholderConfigurer
,它将会替换dataSource的属性值。
<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
PropertyPlaceholderConfigurer
如果在指定的Properties
文件中找不到你想使用的属性,它还会在Java的System
类属性中查找。这个行为可以通过设置systemPropertiesMode
属性来定制,它有三个值:让配置一直覆盖、让它永不覆盖及让它仅仅在属性文件中找不到该属性时才覆盖。请参考PropertiesPlaceholderConfigurer
的JavaDoc以获得更多的信息。
另一个bean工厂后置处理器PropertyOverrideConfigurer
类似于PropertyPlaceholderConfigurer
。但是与后者相比,前者对于bean属性可以有缺省值或者根本没有值。如果起覆盖作用的Properties
文件没有某个bean属性的内容,那么将使用缺省的上下文定义。
bean工厂并不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻知道覆盖配置是否被使用了。在多个PropertyOverrideConfigurer
实例中对一个bean属性定义了不同的值时,最后定义的值将被使用(由于覆盖机制)。
Properties文件的配置应该是如下的格式:
beanName.property=value
一个properties文件可能是下面这样的:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
这个示例文件可用在这样一个bean容器:包含一个名为dataSource的bean,并且这个bean有driver和url属性。
注意它也支持组合的属性名称,只要路径中每个组件除了最后要被覆盖的属性外全都是非空的(比如通过构造器来初始化),在下例中:
foo.fred.bob.sammy=123
foo
bean的fred
属性的bob
属性的sammy
属性被设置为数值123。
工厂bean需要实现org.springframework.beans.factory.FactoryBean
接口。
FactoryBean
接口是插入到Spring IoC容器用来定制实例化逻辑的一个接口点。如果你有一些复杂的初始化代码用Java可以更好来表示,而不是用(可能)冗长的XML,那么你就可以创建你自己的FactoryBean
,并在那个类中写入复杂的初始化动作,然后把你定制的FactoryBean
插入容器中。
FactoryBean
接口提供三个方法:
Object getObject()
:返回一个由这个工厂创建的对象实例。这个实例可能被共享(取决于isSingleton()的返回值是singleton或prototype)。boolean isSingleton()
:如果要让这个FactoryBean创建的对象实例为singleton则返回true,否则返回false。Class getObjectType()
:返回通过getObject()方法返回的对象类型,如果该类型无法预料则返回null。
在Spring框架中FactoryBean
的概念和接口被用于多个地方;在本文写作时,Spring本身提供的FactoryBean
接口实现超过了50个。
最后,有时需要向容器请求一个真实的FactoryBean
实例本身,而不是它创建的bean。这可以通过在FactoryBean
(包括ApplicationContext
)调用getBean
方法时在bean id前加'&'
(没有单引号)来完成。因此对于一个假定id为myBean
的FactoryBean
,在容器上调用getBean("myBean")
将返回FactoryBean
创建的bean实例,但是调用getBean("&myBean")
将返回FactoryBean
本身的实例。