提示:由于作者水平和时间有限,请仅以参考的态度阅读。
引言
在使用SpringMVC做Web开发的时候,为了便于统一管理配置项,常常会看到用占位符的配置方式。这样,可以将分散在spring配置文件中的配置项的值集中到一个(多个)属性文件中,方便管理。
比如定义了一个bean,属性的值使用占位符,如下(applicationContext.xml)
<bean id= "funnelData" class ="com.company.project.web.FunnelData">
<property name="name" value="${funnel.name}" />
<property name="value" value="${funnel.value}" />
</bean>
接着在其他properties文件中指定占位符所代表的值,如下(bean.properties)
funnel.name= kiseki
funnel.value=1234
然后告诉spring这个properties文件的位置,这是通过配置PropertyPlaceholderConfigurer的bean来做到的,如下(applicationContext.xml)
<bean id= "placeHolder"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
<property name="location" >
<value>classpath:/application.properties</value >
</property>
</bean>
分析
虽然在3.1开始官方文档推荐优先使用PropertySourcesPlaceholderConfigurer取代PropertyPlaceholderConfigurer,但是研究分析下PropertyPlaceholderConfigurer还是会有不少收获。spring占位符替换主要涉及到BeanFactoryPostProcessor接口和PropertyPlaceholderConfigurer、PlaceholderConfigurerSupport、PropertyResourceConfigurer三个类。
Spring提供了的一种叫做BeanFactoryPostProcessor的容器扩展机制。它允许我们在容器实例化对象之前,对容器中的BeanDefinition中的信息做一定的修改(比如对某些字段的值进行修改,这就是占位符替换的根本)。于是就需要说下BeanFactoryPostProcessor接口了,以下BeanFactoryPostProcessor的定义
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
在web项目的spring上下文初始化中,spring在实例化bean之前,会先实例化出实现了BeanFactoryPostProcessor接口的bean,并调用postProcessBeanFactory方法,对BeanFactory中的BeanDefinition进行处理。
看看PropertyPlaceholderConfigurer、PlaceholderConfigurerSupport、PropertyResourceConfigurer三个类的定义
public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport
public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer
implements BeanNameAware, BeanFactoryAware
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport
implements BeanFactoryPostProcessor, PriorityOrdered
可以看到PropertyPlaceholderConfigurer类的祖先类PropertyResourceConfigurer实现了BeanFactoryPostProcessor接口。
先看看PropertyResourceConfigurer的postProcessBeanFactory()方法
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);
}
}
这个方法整合好Properties,然后以BeanFactory和Properties作为参数调用PropertyPlaceholderConfigurer的processProperties方法。
接着看PropertyPlaceholderConfigurer的processProperties()方法
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
this.doProcessProperties(beanFactoryToProcess, valueResolver);
}
这个类就实例化了一个StringValueResolver对象,然后用BeanFactory和StringValueResolver对象调用PlaceholderConfigurerSupport#doProcessProperties()
再看PlaceholderConfigurerSupport的doProcessProperties()方法
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
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);
}
这个方法就会取出BeanFactory中的BeanDefinition,然后循环处理除了本身以外的bean的占位符替换
扩展
在了解了PropertyPlaceholderConfigurer做占位符替换的流程之后,我们应该有所启发:
1、要定制自己的占位符替换实现,入口就在BeanFactoryPostProcessor接口。实现BeanFactoryPostProcessor接口,并替换掉PropertyPlaceholderConfigurer即可。
2、占位符替换过程中,最主要的是Properties,整合出自己的Properties后,spring现成的很多代码可以继续使用。
目前我们采用Zookeeper做配置中心,用于管理多个APP实例上的配置,而基本的思路就是实现BeanFactoryPostProcessor接口,从Zookeeper上取相应节点,构造出Properties。