spring容器扩展之BeanFactoryPostProcessor
pring允许BeanFactoryPostProcessor在容器创建bean之前读取bean配置元数据,并可进行修改。例如增加bean的属性和值,重新设置bean是否作为自动装配的侯选者,重设bean的依赖项等等。
在srping配置文件中可以同时配置多个BeanFactoryPostProcessor,并通过在xml中注册时设置’order’属性来控制各个BeanFactoryPostProcessor的执行次序。或者实现Ordered
接口来控制执行次序。
接口定义如下
public interface BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
接口只有一个方法postProcessBeanFactory。该方法的参数是ConfigurableListableBeanFactory类型,实际开发中,我们常使用它的getBeanDefinition()方法获取某个bean的元数据定义:
spring使用PropertyPlaceholderConfigurer将bean的属性值从bean定义替换到使用标准Java Properties
文件。这样做使部署应用程序的人员能够定制特定于环境的属性,如数据库url和密码,而不需要修改主XML定义文件或容器文件带来的复杂性或风险。eg
<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>
jdbc.properties可以这样写
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
这样${jdbc.username}占位符被替代为sa,来看看是怎么实现的?
先看PropertyPlaceholderConfigurer的父类PropertyResourceConfigurer,PropertyResourceConfigurer类实现了BeanFactoryPostProcessor接口,实现方法如下
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.具体由子类实现
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
来看下PropertyResourceConfigurer的processProperties方法实现
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage());
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
beanFactoryToProcess.resolveAliases(valueResolver);就是具体的替换占位符的过程