PropertyPlaceholderConfigurer的一个小坑

之前看到有同学写了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}不能被正常替换掉

解决方法:

1.在子容器中也增加PropertyPlaceholderConfigurer bean的声明

在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"/>

2.使用Util命名空间

<util:properties id="settings" location="WEB-INF/classes/xxx.properties" />

当然要在配置文件增加util的namespace,这样就不用定义两次PropertyPlaceholderConfigurer 变量使用#{settings['xxxx']}即可

总结: 因为Spring父子容器的加载顺序的原因,对于实现了BeanFactoryPostProcessor接口的类,如果要在父子容器中都要有作用的话,就需要都在各自的配置文件中声明。 如下所示:

本文来自网易实践者社区,经作者方向授权发布。