Spring property-placeholder and generated properties

Categories: Java

Spring dependency injection is a weird beast sometimes. Today I got bitten by some behaviour that makes no sense at all unless you know that context:property-configurator is implemented as a BeanPostProcessor, and are familiar with what a BeanPostProcessor does. As it happens, I did a lot of work with Spring internals about 5 years ago, but the details have rapidly faded. Today was an unwelcome reminder…

In short: when using Spring 3.0 and XML configuration, ${varname} is not expanded within a bean-factory that a property-placeholder references via properties-ref.

Spring’s <context:property-placeholder> tag allows bean definitions to reference values from property files; once one (or more) property placeholders are defined, then value="${foo.bar}" gets expanded using whatever properties were passed to the property-placeholder. Useful.

However for a particular use-case, I needed to expose properties that weren’t simply loaded from an external file. Simple - just write a factory bean that returns a properties object, then pass that object to property-placeholder using properties-ref="beanname", right? Well, yes - except that the factory itself needed to be configured - and most conveniently via a properties file.

The result was some XML that looked like this:

    <!-- Retrieve a Properties object from the OSGi ConfigurationAdmin service, and expose for injection into Spring beans -->
    <osgix:cm-properties id="base.properties" persistent-id="baseprops"/>
    <ctx:property-placeholder order="2" properties-ref="base.properties"/>

    <!-- Generate some more properties to be exposed for injection into Spring beans -->
    <bean id="propGenerator" class="example.PropGenerator">
      <constructor-arg value="${my.property}"/>
    </bean>
    <bean id="generated.properties" factory-bean="propGenerator" factory-method="generateProps"/>
    <ctx:property-placeholder ignore-unresolvable="true" order="1" properties-ref="generated.properties"/>

    ...stuff that uses the above properties via expressions like ${propname}...

And - it didn’t work. In the PropGenerator constructor, the parameter was literally "${my.properties}" - the variable wasn’t getting expanded at all.

After much research, the cause turned out to be (as mentioned above) how Spring BeanPostProcessor objects work.

Spring’s context setup is basically:

  • parse xml to generate BeanDefinition objects
  • for all definitions whose type is a BeanPostProcessor
    • instantiate that object [1]
  • for each BeanDefinition
    • run all the bean-post-processors; these may modify the BeanDefinition [2]
    • instantiate the bean using the updated BeanDefinition

A property-placeholder is a BeanPostProcessor which does its work at point [2], expanding ${foo} within any BeanDefinition’s value attributes.

Because generated.properties is a dependency of a property-placeholder instantiated at [1], it must also be instantiated and run at stage [1] (which is not the case for normal beans). It therefore does not get any variable-expansion support, as that only occurs at [2].

Note also that when multiple property-placeholders exist, then they are all run at [2], in the order specified by their order attribute. If one expands a variable, then the next one simply never sees that the variable ever existed (the BeanDefinition has been updated). Note also that all property-placeholders except the last one to run must have ignore-unresolvable='true', otherwise it will report an unknown variable even if a later processor might be able to resolve that variable.

Interestingly, the failure to expand ${my.property} does not cause an error to be reported - because the message variable undefined is issued by a property-placeholder BeanPostProcessor - and these simply don’t get executed on this bean’s BeanDefinition.

The best solution I could find is:

    <!-- Retrieve a Properties object from the OSGi ConfigurationAdmin service, and expose for injection into Spring beans -->
    <osgix:cm-properties id="base.properties" persistent-id="baseprops"/>
    <ctx:property-placeholder order="2" properties-ref="base.properties"/>

    <!-- Generate some more properties to be exposed for injection into Spring beans -->
    <bean id="propGenerator" class="example.PropGenerator">
      <constructor-arg >
        <!-- ${my.property} doesn't work yet because beanfactorypostprocessors (including property-placeholders) are still being initialized -->
        <bean factory-bean="base.properties" factory-method="get">
            <constructor-arg value="my.property"/>
        </bean>
      </constructor-arg>
    </bean>
    <bean id="generated.properties" factory-bean="propGenerator" factory-method="generateProps"/>
    <ctx:property-placeholder ignore-unresolvable="true" order="1" properties-ref="generated.properties"/>

    ...stuff that uses the above properties via expressions like ${propname}...

And the lesson learned today is: multiple property-placeholders are fine, but:

  • they should be given an “order” attribute;
  • all except the one with the highest “order” value (last to run) should have ignore-unresolvable="true";
  • those rare beans that provide properties for a property-placeholder cannot use ${..}. Instead, use factory-bean to invoke the get method directly on a properties object.
comments powered by Disqus