一般地,应用程序开发者不需要实现ApplicationContext
的子类。作为替代,可以通过指定接口实现插入扩展Spring IoC容器。下面的章节描述了这些集成接口。
4.8.1
使用BeanPostProcessor定制beans
BeanPostProcessor
接口定义了回调方法,这样你可以提供实现自己的实例化逻辑(或者重写容器默认的),依赖解决逻辑,等等。如果你想在Spring容器在完成实例化,配置和初始化一个bean后,实现自定义的逻辑,你可以插入一个或者更多的BeanPostProcessor实现。
你可以定义多个BeanPostProcessor实例,并且你可以通过设置order属性控制这些BeanPostProcessors执行的顺序。只要BeanPostProcessor实现了Ordered接口你就可以设置这个属性了。如果你写了自己的BeanPostProcessor,你就要考虑实现Ordered接口。
注意:BeanPostProcessors操作在bean或者对象实例上;就是说,Spring IoC容器实例化一个bean实例并且之后BeanPostProcessors做它们的工作。
BeanPostProcessors
范围位于pre-container。其仅在使用容器继承中有用。如果你在一个容器中定义一个BeanPostProcessor,其将是在那个容器内部
post-process的beans。换句话说,在一个容器中定义的bean不被在另一个容器中定义的BeanPostProcessorpost-processed,即使两个容器属于相同的层级。
为改变真实的bean定义(blueprint定义了bean),你需要使用一个BeanFactoryProcessor。
org.springframework.beans.factory.config.BeanPostProcessor
接口由两个回调方法组成。当这样的一个类以post-processor注册到容器内时,对于由这个容器创建的每个bean实例,post-processor在调用容器初始化方法前(比如
InitializingBean的afterPropertiesSet()方法和任何声明的init方法)和任何bean实例化调用后从容器内获取一个回调。Post-processor可以获取那个bean实例的所有动作,包括完全忽略回调。一个bean post-processor一般地检查回调接口或者可能用一个代理包装一个bean。一些Spring AOP底层类以bean post-processor实现来提供包装代理逻辑。
一个ApplicationContext自动查找在配置元数据中定义的beans,实现了BeanPostProcessor接口。ApplicationContext以post-processor注册这些bean,这样它们可以在其后面的创建中调用。它们也可以如同其他bean一样销毁。
程序化注册BeanPostProcessors
注册BeanPostProcessor的推荐方式是上面提及的ApplicationContext的自动查找,也可能程序化注册一个BeanPostProcessor,借助ConfigurableBeanFactory
的addBeanPostProcessor方法。这个对于要在注册前进行评估判断条件的时候,或者甚至同级中上下文之间复制bean post Procesors很有用。注意然而程序化增加的BeanPostProcessors不遵循Ordered接口。这里它是注册的顺序,同时指定了执行的顺序。也要注意那个程序化注册的BeanPostProcessors总是通过自动查找在注册前处理,而不管任何确定的顺序。
BeanPostProcessor
和 自动代理AOP
实现了BeanPostProcessor接口的类是特殊的并且会被容器特殊对待。所有的BeanPostProcessors和它们直接引用的beans在启动时实例化,其作为那个ApplicationContext的指定启动阶段的一部分。下一步,所有的BeanPostProcessors在一个存储的fashion中注册并应用于容器内所有的将来的beans。因为AOP自动代理以一个BeanPostProcessor自身实现,不管是BeanPostProcessors还是直接相关的beans,都不能合格的自动代理,而是切面编织进去。
(neither BeanPostProcessors
nor the beans theyreference directly are eligible for auto-proxying, and thus do not have aspectswoven into them)----翻译待定
对于任何这样的bean,你应该看到一些信息日志:“Bean foo is not eligiblefor getting processed by all BeanPostProcessor interfaces (for example: noteligible for auto-proxying”
下面的例子显示了如何在一个应用程序中写,注册和使用BeanPostProcessors。
例子:Hello World BeanPostProcessor-style
第一个例子演示了基本的用法。这个例子显示了一个自定义的BeanPostProcessor实现,在容器创建每个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-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/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一样依赖注入。
下面简单的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
4.8.1.2 例子:RequiredAnnotationBeanPostProcessor
使用回调接口或者使用BeanPostProcessor自定义实现的注解是扩展Spring IoC容器的通用方法。例子之一是Spring的RequiredAnnotationBeanPostProcessor------一个BeanPostProcessor实现,其在Spring分布中流转,确保了以注解标识的beans的JavaBean属性可以以一个值依赖注入。
4.8.2
使用BeanFactoryPostProcessor定制配置元数据
我们将查看的下一扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor
.。这个接口的语义与BeanPostProcessor的语义很相似。主要的一个区别是:BeanFactoryPostProcessor
s操作在bean配置元数据上;即,SpringIoC容器允许BeanFactoryPostProcessors读取配置元数据并可能在容器实例化任何bean之前改变这些配置元数据。
你可以配置多个BeanFactoryPostProcessors,并且通过设置order属性可以控制这些BeanFactoryPostProcessor的执行顺序。然而,只有实现了Ordered接口的BeanFactoryPostProcessor才能设置这个属性。如果你自定义BeanFactoryPostProcessor,应该考虑实现Ordered接口。
注意:
如果你想改变bean实例(即,从配置元数据中创建的对象),稍后你需要使用BeanPostProcessor。虽然技术上在BeanFactoryPostProcessor内可以与bean实例一起工作是可能的,但是这样做会导致bean实例化不完全,违反了标准容器的生命周期。这个可能在绕过bean postprocessor中产生负面效果。
BeanFactoryPostProcessors
范围是pre-container。仅在容器级别有效。如果你在一个容器内定义了一个BeanFactoryPostProcessor,就仅在那个容器内应用。一个容器内的bean定义将不能在另一个容器内由BeanFactoryPostProcessor post-processed,即使是两个容器处于相同的界别。
一个beanfactory post-processor是自动执行的,当其在ApplicationContext内部申明时,为了适应容器中定义的配置元数据的变化。Spring包含了大量的预定义bean 工厂post-processors,比如
PropertyOverrideConfigurer
和PropertyPlaceholderConfigurer。也能使用自定义的BeanFactoryPostProcessor,例如,注册自定义的属性编辑。
一个ApplicationContext自动查找部署在其中的所有的bean,其实现了BeanFactoryPostProcessor接口。它在适当地时候使用这些beans作为bean factory post-processors。你可以如同其他bean一样部署这些bean。
注意:
对于BeanPostProcessors,一般地你不想配置BeanFactoryPostProcessors来延迟初始化。如果没有其他的bean引用一个bean(Factory)PostProcessor,post-processor不将实例化。那么,将忽略其延迟实例化,并且即使你在<bean/>元素中设置default-lazy-init属性为true,
Bean(Factory)PostProcessor
也将马上实例化。
4.8.2.1
例子:
PropertyPlaceholderConfigurer
使用PropertyPlaceholderConfigurer从一个独立文件的bean定义中使用标准Java属性格式设置合适的值。这样做能够使的开发人员部署应用定制化指定环境的属性,比如数据库的URLs和密码,不需要冒着修改容器主要XML文件的风险。
考虑下面的基于XML配置元数据的片段,使用占位符定义了DataSource。下面的例子显示了内部属性文件的属性配置。在运行时,PropertyPlaceholderConfigurer被应用到元数据,将替代DataSource的一些属性。这个替代值由${属性名}格式的占位符指定。,其遵循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属性文件中定义。
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介绍的命名空间中,可能使用一个专用配置元素配置合适的占位符。在location属性中可以使用逗号隔开提供更多的locations。
PropertyPlaceholderConfigurer
不仅仅查找你定义的属性文件中的属性。默认地,它也检查Java系统属性,如果其不能在一个指定属性文件中找到一个属性。你可以通过设置配置中的systemPropertiesMode属性定制化行为,使用下面提供的整形值的一个。
Never(0):
从不检查系统属性
Fallback
(1):如果在指定的属性文件中找到可用值,查找系统属性。这是默认的。
Oberride
(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创建时其分解就会失败,其在preInstantiateSingletons
()阶段,针对非延迟初始化bean的ApplicationContext。
4.8.2
.2 例子:
PropertyOverrideConfigurer
PropertyOverrideConfigurer
,另一个bean 工厂post-processor,与PropertyPlaceholderConfigurer相似,但也与后者不是完全像,最初的定义可以有bean的默认值或者没有值。如果重写的属性文件对于某一bean属性没有设置,就使用默认上下文定义的值。
注意bean定义不在意是否重写,因此从XML定义文件中发现使用重写配置不是很明显。在多PropertyOverrideConfigurer实例情况中,对于相同bean属性的定义不同的值,最后的一个覆盖前面的,就是因为重写机制。
属性文件定义格式如下:
beanName.property=value
例如:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
这个例子文件可以用于容器定义中,包含了一个叫做dataSource的bean,其有driver和url属性。
复杂的属性名也支持,只要路径的每个组件,除了最后的需要重写的属性是非null的(假定构造器初始化)。在这个例子中,
foo.fred.bob.sammy=123
foo bean的fred属性的bob属性的sammy属性的数量值为123.
注意:
指定的重写的值总是文字的值;它们不能翻译成bean的引用。这个惯例也适用XMLbean定义的的初始值指定一个bean‘的引用。
在Spring 2.5介绍的Context命名空间中,使用一个指定的配置元素来配置属性重写。
<context:property-overridelocation="classpath:override.properties"/>
4.8.3 Customizing instantiation logic with a FactoryBean(自定义一个FactoryBean的实例化逻辑)
实现了org.springframework.beans.factory.FactoryBean
接口的对象自身就是工厂。
FactoryBean
是Spring IoC容器的实例化逻辑的a point of pluggability。如果你有复杂的初始化代码,其在Java的中很好的表示代替XML中冗余的表示,你可以创建自己的FactoryBean,在那个类中写复杂的初始化逻辑,并以插件形式将自定义的FactoryBean插入那个容器。
FactoryBean
接口提供了三个方法:
1 Object getObject
() 返回工厂创建的对象的实例。这个实例可能被共享,取决于这个工厂返回对象是singleton还是prototype。
2 boolean isSingleton
() 如果这个对象返回的是singleton则返回true,否则false。
3 Class getObjectType
() 返回有getObject()返回的对象类型或者null,如果类型不可知。
FactoryBean
概念和接口用于Spring框架的许多地方。在Spring中超过50多个类实现了FactoryBean接口。
当你需要请求容器一个确切的FactoryBean实例,而不是自己创建,以&开始防止bean的id前面,当调用ApplicationContext的getBean()方法时。所以对于给定mybean id的FactoryBean,调用容器的getBean(“myBean”),返回FactoryBean的子类。然而,调用getBean(“&mybean”)返回FactoryBean自身