之前看到有同学写了PropertyPlaceholderConfigurer的一篇关于属性名的文章, 碰巧自己在开发中也遇到了PropertyPlaceholderConfigurer的一个小问题,在此分享一下 先看配置文件 xxx-properties.xml
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:xxx.properties</value>
</list>
</property>
</bean>
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/xxx-db.xml
classpath:spring/xxx-properties.xml
classpath:spring/xxx-service.xml
</param-value>
</context-param>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:xxx-servlet.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
xxx-servlet.xml
<context:component-scan base-package="com.xxx.xxxx.controller" />
....
这样在xxx-service.xml中定义的Bean就可以用 ${xxxx}
正常使用properties里的值 但是我想在controller里使用${xxxx}
来注入属性的时候,却始终返回${xxxx}
,看来是没有替换掉。
那么来看代码,PropertyPlaceholderConfigurer这个类的继承结构:
从这个结构图中可以看出PropertyPlaceholderConfigurer这个类间接继承了BeanFactoryPostProcessor接口。这是一个很特别的接口,当Spring加载任何实现了这个接口的bean的配置时,都会在bean工厂载入所有bean的配置之后执行postProcessBeanFactory方法。在PropertyResourceConfigurer类中实现了postProcessBeanFactory方法,在方法中先后调用了mergeProperties,convertProperties,processProperties这三个方法,分别得到配置,将得到的配置转换为合适的类型,最后将配置内容告知BeanFactory
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(), ex);
}
}
}
// 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.getBeanDefinitionNames() 这里只获取当前的beanFactory所定义的bean,所以在xxx-service.xml中定义的Bean就可以正常的被替换掉,而xxx-servlet.xml里的bean这个时候还没有被创建(这个是在子容器中创建的),所以controller里的${xxxx}
不能被正常替换掉
解决方法:
在xxx-servlet.xml增加PropertyPlaceholderConfigurer bean的配置
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:xxx.properties</value>
</list>
</property>
</bean>
或者
<import resource="classpath:spring/xxx-properties.xml"/>
<util:properties id="settings" location="WEB-INF/classes/xxx.properties" />
当然要在配置文件增加util的namespace,这样就不用定义两次PropertyPlaceholderConfigurer 变量使用#{settings['xxxx']}即可
总结: 因为Spring父子容器的加载顺序的原因,对于实现了BeanFactoryPostProcessor接口的类,如果要在父子容器中都要有作用的话,就需要都在各自的配置文件中声明。 如下所示:
本文来自网易实践者社区,经作者方向授权发布。