3.7.1. 用BeanPostProcessor
定制bean
我们关注的第一个扩展点是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
时。
BeanPostProcessors
和AOP自动代理(auto-proxying)
实现了BeanPostProcessor
接口的类是特殊的, 会被容器特别对待. 所有 BeanPostProcessors
和直接引用的bean 会作为ApplicationContext
一部分在启动时初始化, 然后所有的BeanPostProcessors
会注册入一个列表并应用于之后的bean。AOP自动代理实现了BeanPostProcessor
,所以BeanPostProcessors
或bean的直接引用不会被自动代理(因此不会被aspects"织入")。
对这些bean来说,你可能看到下面的日志信息:“Bean 'foo' is not eligible for getting processed by all BeanPostProcessors (如:不能被auto_proxying)”
关于如何在ApplicationContext
中编写、注册并使用BeanPostProcessor
,会在接下的例子中演示。
3.7.1.1. 使用BeanPostProcessor
的Hello World示例
第一个实例似乎不太吸引人,但是它适合用来阐述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 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.5.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.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
3.7.1.2. RequiredAnnotationBeanPostProcessor
示例
在Spring的BeanPostProcessor
实现中调用标志接口或使用注解是扩展Spring IoC容器的常用方法。对于注解的用法详见第 25.3.1 节 “@Required
”,这里没有做深入的说明。通过定制BeanPostProcessor
实现,可以使用注解来指定各种JavaBean属性值并在发布的时候被注入相应的bean中。
3.7.2. 用BeanFactoryPostProcessor
定制配置元数据
我们将看到的下一个扩展点是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"'
。
3.7.2.1. PropertyPlaceholderConfigurer
示例
PropertyPlaceholderConfigurer
是个bean工厂后置处理器的实现,可以将BeanFactory
定义中的一些属性值放到另一个单独的标准Java Properties
文件中。这就允许用户在部署应用时只需要在属性文件中对一些关键属性(例如数据库URL,用户名和密码)进行修改,而不用对主XML定义文件或容器所用文件进行复杂和危险的修改。
考虑下面的XML配置元数据定义,它用占位符定义了DataSource
。我们在外部的Properties
文件中配置一些相关的属性。在运行时,我们为元数据提供一个PropertyPlaceholderConfigurer
,它将会替换dataSource的属性值。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/foo/jdbc.properties</value>
</property>
</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
在Spring 2.5中,context
名字空间可能采用单一元素属性占位符的方式(多个路径提供一个逗号分隔的列表)
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
如果在指定的Properties
文件中找不到你想使用的属性,它还会在Java的System
类属性中查找。这个行为可以通过设置systemPropertiesMode
属性来定制,它有三个值:让配置一直覆盖、让它永不覆盖及让它仅仅在属性文件中找不到该属性时才覆盖。请参考PropertiesPlaceholderConfigurer
的JavaDoc以获得更多的信息。
类名替代
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会创建失败(当对非延迟实例化bean执行ApplicationContext
的preInstantiateSingletons()
方法的情况下)。
3.7.2.2. PropertyOverrideConfigurer
示例
另一个bean工厂后置处理器PropertyOverrideConfigurer
类似于PropertyPlaceholderConfigurer
。但是与后者相比,前者对于bean属性可以有缺省值或者根本没有值。如果起覆盖作用的Properties
文件没有某个bean属性的内容,那么将使用缺省的上下文定义。
bean工厂并不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻知道覆盖配置是否被使用了。在多个PropertyOverrideConfigurer
实例中对一个bean属性定义了不同的值时,最后定义的值将被使用(由于覆盖机制)。
Properties文件的配置应该是如下的格式:
beanName.property=value
An example properties file might look like this:
一个properties文件可能是下面这样的:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
这个示例文件可用在这样一个bean容器:包含一个名为dataSource的bean,并且这个bean有driver和url属性。
注意它也支持组合的属性名称,只要路径中每个组件除了最后要被覆盖的属性外全都是非空的(比如通过构造器来初始化),在下例中:
foo.fred.bob.sammy=123
... the sammy
property of the bob
property of the fred
property of the foo
bean is being set to the scalar value 123
.
foo
bean的fred
属性的bob
属性的sammy
属性被设置为数值123。
3.7.3. 使用FactoryBean
定制实例化逻辑
工厂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
本身的实例。
相关推荐
BeanPostProcessor允许在bean初始化前后进行自定义操作,而BeanFactoryPostProcessor则在bean加载但未实例化之前执行特殊处理,例如PropertyPlaceholderConfigurer可以用于处理属性文件中的占位符替换。 总的来说,...
例如,`PropertyPlaceholderConfigurer`就是在这里处理属性占位符的替换。 `registerBeanPostProcessors(beanFactory)`注册了BeanPostProcessors,这些处理器会在Bean初始化时介入,执行额外的操作,如AOP代理、...
- `PropertyPlaceholderConfigurer`:用于解析属性占位符,如`${property}`,将其替换为实际的属性值。 - `CustomEditorConfigurer`:允许注册自定义编辑器,用于将字符串转换为特定类型的数据。 3. **事件处理**...
可以使用`PropertyPlaceholderConfigurer`来载入属性文件,并在其他地方使用`${database.url}`等方式引用属性值。 #### 11. `CustomEditorConfigurer`的使用 通过`CustomEditorConfigurer`可以注册自定义的`...
`PropertyPlaceholderConfigurer`是一个BeanFactoryPostprocessor,用于在运行时替换配置文件中的占位符。 ##### 3.8.2 PropertyOverrideConfigurer `PropertyOverrideConfigurer`也是BeanFactoryPostprocessor的一...
2. **BeanPostProcessor接口**:实现这个接口的类可以定义两个方法`postProcessBeforeInitialization()`和`postProcessAfterInitialization()`,分别在Bean初始化前和后进行处理。 3. **@PostConstruct注解**:在...
这一过程涉及到Spring的`PropertyPlaceholderConfigurer`、`BeanPostProcessor`、`InstantiationAwareBeanPostProcessor`等接口和类。 **工具的使用** Spring提供了多种方式来加载配置,例如XML配置文件、Java配置...
2. **BeanPostProcessor**:BeanPostProcessor是一个特殊的Bean,它可以在Bean的实例化前后进行拦截操作,常用于自定义Bean的初始化逻辑。 3. **BeanFactoryPostProcessor**:BeanFactoryPostProcessor是在...
- **PropertyPlaceholderConfigurer** 和 **PropertyOverrideConfigurer**:用于在运行时动态替换Bean配置中的占位符。 ### 结论 Spring框架以其强大的功能和灵活性,成为Java企业级应用开发的首选框架之一。通过...
- **使用BeanPostProcessors自定义Bean**:BeanPostProcessor接口允许开发者自定义Bean的初始化过程。 #### 3.7 使用BeanFactoryPostProcessors自定义Bean工厂 - **PropertyPlaceholderConfigurer**:用于替换配置...
不使用XML定义档进行 Bean设置 Aware 相关介面 BeanPostProcessor BeanFactoryPostProcessor PropertyPlaceholderConfigurer PropertyOverrideConfigurer CustomEditorConfigurer ...
PropertyPlaceholderConfigurer PropertyResourceConfigurer PropertyValue PropertyValues PropertyValuesEditor PrototypeAspectInstanceFactory PrototypeTargetSource ProxyConfig ProxyFactory ...
PropertyPlaceholderConfigurer示例 3.7.2.2. PropertyOverrideConfigurer示例 3.7.3. 使用FactoryBean定制实例化逻辑 3.8. ApplicationContext 3.8.1. 利用MessageSource实现国际化 3.8.2. 事件 3.8.3. 底层资源的...
Not Using Commons Logging ................................................................... 12 Using SLF4J ..............................................................................................
Not Using Commons Logging ................................................................... 12 Using SLF4J ..............................................................................................