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, usefactory-bean
to invoke theget
method directly on a properties object.