1.8 容器扩展
通常,应用程序开发人员不需要继承ApplicationContext实现类。相反,可以通过插入特殊集成接口的实现来扩展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自定义配置元数据中所述。
org.springframework.beans.factory.config.BeanPostProcessor 接口恰好由两个回调方法组成。当这样的类作为后处理程序注册到容器时,对于容器创建的每个bean实例,后处理程序在调用容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何声明的init方法)之前和任何bean初始化回调之后,都会从容器获得一个回调。后处理程序可以对bean实例采取任何操作,包括完全忽略回调。bean后处理程序通常检查回调接口,或者它可以用代理包装bean。一些Spring AOP基础结构类被实现为bean后处理程序,以便提供代理包装逻辑。
ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。ApplicationContext将这些bean注册为后处理程序,以便稍后在创建bean时调用它们。Bean后处理程序可以以与任何其他Bean相同的方式部署在容器中。
请注意,当通过在配置类上使用@Bean工厂方法来声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.bean .factory.config.BeanPostProcessor接口,清楚地表明该bean的后处理器性质。否则,ApplicationContext不能在完全创建它之前按类型自动检测它。由于需要早期实例化BeanPostProcessor,以便应用于上下文中其他bean的初始化,因此这种早期类型检测非常关键。
以编程方式注册BeanPostProcessor实例
虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您可以通过使用addBeanPostProcessor方法对ConfigurableBeanFactory以编程方式注册它们。当您需要在注册之前评估条件逻辑,甚至在层次结构中跨上下文复制bean后处理器时,这可能很有用。但是请注意,以编程方式添加的BeanPostProcessor实例并不尊重Ordered接口。在这里,是登记的顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前处理,而不管任何显式的顺序如何。
BeanPostProcessor实例和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,容器以不同的方式对待它们。它们直接引用的所有BeanPostProcessor实例和bean都在启动时实例化,作为ApplicationContext特殊启动阶段的一部分。接下来,以排序方式注册所有BeanPostProcessor实例,并应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以无论是BeanPostProcessor实例还是它们直接引用的bean都不适合进行自动代理,因此它们没有嵌入方面。
对于任何这样的bean,您都应该看到一条信息日志消息:bean someBean不适合被所有BeanPostProcessor接口处理(例如:不适合自动代理)。
如果您通过使用自动装配或@Resource(可能退回到自动装配)将bean连接到BeanPostProcessor中,那么Spring在搜索类型匹配依赖项候选时可能会访问意想不到的bean,因此,使它们不符合自动代理或其他类型的bean后处理的条件。例如,如果您有一个用@Resource标注的依赖项,其中字段或setter名称与bean声明的名称不直接对应,并且没有使用name属性,Spring将访问其他bean以按类型匹配它们。
下面的示例展示了如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例。
示例:Hello World, beanpostprocessor样式
第一个例子说明了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现在容器创建每个bean时调用toString()方法,并将结果字符串打印到系统控制台。
下面的清单显示了自定义BeanPostProcessor实现类定义:
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 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>
<!--
当上述bean(信使)实例化时,此自定义
BeanPostProcessor实现将事实输出到系统控制台
-->
<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
示例:AutowiredAnnotationBeanPostProcessor
将回调接口或注释与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。一个例子是Spring的AutowiredAnnotationBeanPostProcessor——一个随Spring发行版一起发布的BeanPostProcessor实现,它自动连接带注释的字段、setter方法和任意配置方法。
1.8.2. 使用BeanFactoryPostProcessor定制配置元数据
我们查看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor 。该接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor操作bean配置元数据。也就是说,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中声明bean工厂后处理器时,将自动运行它,以便将更改应用到定义容器的配置元数据。Spring包括许多预定义的bean工厂后处理程序,例如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor—例如,注册自定义属性编辑器。
ApplicationContext自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候使用这些bean作为bean工厂的后处理程序。您可以像部署任何其他bean一样部署这些后处理器bean。
与BeanPostProcessors一样,通常不希望将BeanFactoryPostProcessors配置为延迟初始化。如果没有其他bean引用bean (Factory)PostProcessor,则该后处理器根本不会被实例化。因此,将其标记为惰性初始化将被忽略,并且即使您在<beans />元素声明中将default-lazy-init属性设置为true, Bean(Factory)PostProcessor也将被急切地实例化。
示例:类名替换PropertySourcesPlaceholderConfigurer
您可以使用PropertySourcesPlaceholderConfigurer,通过使用标准的Java Properties格式将bean定义中的属性值外部化到单独的文件中。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库url和密码,而无需修改容器的一个或多个主要XML定义文件的复杂性或风险。
考虑以下基于xml的配置元数据片段,其中定义了一个具有占位符值的DataSource:
<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}的占位符,它遵循Ant、log4j和JSP EL风格。
实际值来自另一个标准Java属性格式的文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,${jdbc. xmlUsername}字符串在运行时被替换为值,'sa',同样适用于属性文件中匹配键的其他占位符值。PropertySourcesPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,还可以自定义占位符前缀和后缀。
使用Spring 2.5中引入的上下文名称空间,您可以使用专用的配置元素配置属性占位符。您可以在location属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer不仅查找您指定的properties文件中的属性。默认情况下,如果在指定的属性文件中找不到属性,它将检查Spring Environment属性和常规Java System属性。
您可以使用PropertySourcesPlaceholderConfigurer来替换类名,当您必须在运行时选择特定的实现类时,这有时是有用的。下面的例子展示了如何这样做:
<bean <bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/something/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.something.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>如果不能在运行时将类解析为有效的类,则bean的解析将在即将创建时失败,这是在非惰性初始化bean的ApplicationContext的preInstantiateSingletons()阶段。
示例:PropertyOverrideConfigurer
PropertyOverrideConfigurer是另一个bean工厂后期处理器,它类似于PropertySourcesPlaceholderConfigurer,但与后者不同的是,原始定义可以为bean属性提供默认值,也可以完全没有值。如果覆盖的Properties文件没有特定bean属性的条目,则使用默认上下文定义。
注意,bean定义不知道被覆盖,因此从XML定义文件中不能立即看出正在使用覆盖配置程序。在多个PropertyOverrideConfigurer实例为同一个bean属性定义不同值的情况下,由于覆盖机制,最后一个实例胜出。
属性文件配置行采用以下格式:
beanName.property =value
下面的清单显示了该格式的一个示例:
dataSource.driverClassName = com.mysql.jdbc.Driver
dataSource.url = jdbc: mysql: mydb
这个示例文件可以与容器定义一起使用,该容器定义包含一个名为dataSource的bean,该bean具有驱动程序和url属性。
复合属性名也被支持,只要路径的每个组件(除了被覆盖的最终属性)都是非空的(假设是由构造函数初始化的)。在下面的例子中,tom bean的fred属性的bob属性的sammy属性被设置为标量值123:
tom.fred.bob.sammy = 123
指定的重写值总是文字值。它们没有被翻译成bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。
使用Spring 2.5中引入的上下文名称空间,可以使用专用的配置元素来配置属性覆盖,如下面的示例所示:
<context:property-override location="classpath:override.properties"/>
1.8.3. 使用FactoryBean定制实例化逻辑
您可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。
FactoryBean接口是Spring IoC容器实例化逻辑的可插拔点。如果您有复杂的初始化代码,与冗长的XML相比,更好地用Java表达,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。
FactoryBean接口提供了三个方法:
- T getObject():返回该工厂创建的对象的实例。实例可能是共享的,这取决于该工厂返回的是单例还是原型。
- boolean isSingleton():如果FactoryBean返回单例,则返回true,否则返回false。此方法的默认实现返回true。
- 类< ?> getObjectType():返回由getObject()方法返回的对象类型,如果事先不知道该类型,则返回null。
FactoryBean概念和接口在Spring框架中的许多地方都有使用。超过50个FactoryBean接口实现随Spring一起发布。
当您需要向容器请求实际的FactoryBean实例本身而不是它所产生的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前加上&符号(&)。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品,而调用getBean(“&myBean”)将返回FactoryBean实例本身。