Spring 2.x schema based configuration and the PropertyPlaceholderConfigurer

Vincent Partington

Spring 2.0 and later versions offer schema based configuration that allows us to have a more expressive and concise way to specify our configuration. For example, Spring Security 1.0 configuration used to be quite hairy, but became a lot simpler with Spring 2.0.

One disadvantage is that the documentation of the namespaces is not as good as that for regular beans. For regular beans, I can have a look at Javadoc, while for the namespaces that come with Spring the documentation is not up to the same level of detail. Until a Javadoc-like tool can generate this documentation from the schemas, the best documentation is the schemas themselves.

One little problem

But ignoring that issue, I recently ran into a very practical problem with using schema based configuration in combination with a PropertyPlaceholderConfigurer.

The context of my application contained a PropertyPlaceholderConfigurer to allow it to be configured for different environments. Of course it worked like a charm for all my beans. It also worked for my Spring Security configuration, e.g. the settings for the LDAP server:
<security:ldap-server id="adLdap" url="ldap://${ldap.host}:${ldap.port}/${ldap.base.dn}"
    manager-dn="${ldap.connect.dn}" manager-password="${ldap.connect.password}" />

But it stopped working when I also wanted to make the roles the application was using configurable like this:
<security:intercept-url pattern="/remoting/**" access="${security.group.authenticated.name}" />

Now I got this exception from the Spring container when it was wiring up my beans:
java.lang.IllegalArgumentException: Unsupported configuration attributes: ${security.group.authenticated.name}

Apparently this value was not being replaced!

Analysis

It turned out that other people were having this problem as well, and that there's even a few bugs reported about it.

This is how I understand the problem. When an XML file is read by a XmlBeanDefinitionReader, all the XML elements that are not part fo the standard beans namespace are passed to a NamespaceHandler that then creates a bean definition that corresponds to those XML elements.

And after that all the bean definitions that have been created (both by the standard bean parsing and by the NamespaceHandlers) are processed by any BeanFactoryPostProcessors that are there. Such as the PropertyPlaceholderConfigurer.

But not all namespace handlers do it like this. In the case I bumped into, it turned out that the SecurityNamespaceHandler registers the HttpSecurityBeanDefinitionParser to handle the <http> element. And in that class the access configs are not stored in a BeanDefinition but put directly into a RequestKey object. Giving the PropertyPlaceholderConfigurer no change to change the value!

Having discovered the problem, I implemented a solution to the problem. Instead of post-processing the beans, my solution pre-processes the XML file. This has the added advantage that it works for any XML attribute value, not just property values

There are a few different ways to use my solution which I'll list below. Another good source of information on how to use these classes is the test cases in the src/test/java directory in the ZIP file.

PropertyPlaceholderReplacingXmlBeanFactory

The simplest way to use it is where you would ordinarily use a XmlBeanFactory. Instead you now use the com.xebia.springframework.beans.factory.xml.PropertyPlaceholderReplacingXmlBeanFactory like this:
Resource context1resource = new ClassPathResource("beans_with_property_placeholders.xml");
Resource props1resource = new ClassPathResource("beanvalues.properties");
Properties props1 = new Properties();
props1.load(props1resource.getInputStream());
BeanFactory factory = new PropertyPlaceholderReplacingXmlBeanFactory(context1resource, props1);

This has the effect of loading the file beans_with_property_placeholders.xml like the regular XmlBeanFactory would. However, after XML parsing and before the XML is scanned for bean definitions and such, all attribute values (not just property values) are parsed to see if they contain any property placeholders (${...}). If so, they are replaced with the values found in the file beanvalues.properties.

Implementation note: Unlike the property placeholder replacement implementation of Spring itself, this implementation does not check for infinite recursion. That is left as an exercide to the user. ;-)

<xbeans:import-with-property-placeholder-replacement>

However, most of the time you have less control over the factory used to load your beans. If you are building a web application, you are most likely using the XmlWebApplicationContext or something similar. While it is certainly possible to build a PropertyPlaceholderReplacingXmlWebApplicationContext that uses the com.xebia.springframework.beans.factory.xml.PropertyPlaceholderReplacingXmlBeanDefinitionReader instead of the standard one (please feel free to do so :-) ), I thought a more likely use would be where a generic context would import another context file and specify the values to replace while importing. To that end I have written the import-with-property-placeholder-replacement tag and put it in the new namespace http://www.xebia.com/schema/xbeans.

In my case the standard context XML would import the spring security settings while replacing the values of the access attributes with the proper values:
<xbeans:import-with-property-placeholder-replacement
    resource="security-context.xml"
    properties="security.properties">

PropertyPlaceholderReplacingContextLoader

And yet another way to use this stuff is from a JUnit 4.x test case. The @ContextConfiguration annotation takes an optional loader parameter that can be set to com.xebia.springframework.test.context.PropertyPlaceholderReplacingContextLoader. To specify the properties file to use you have to add a @com.xebia.springframework.test.context.ContextConfigurationProperties annotation like this:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = PropertyPlaceholderReplacingContextLoader.class,
    locations = {"/com/xebia/springframework/beans/factory/xml/beans_with_property_placeholders.xml"})
@ContextConfigurationProperties("/com/xebia/springframework/beans/factory/xml/beanvalues.properties")
@TestExecutionListeners(value = { DependencyInjectionTestExecutionListener.class })

Conclusion

Well, implementing this has been an interesting dash through the internals of Spring 2.x. It was interesting to figure out how Spring loads the beans files and how I could hook into it. And implementing my own namespace was fun too. Also, I am interested to see whether and how Spring 3.0 will solve this problem. In the meantime, the code I wrote is available to solve this problem if you bump into it.

And finally, have a very good 2009. May all your code be clean!

Comments (1)

  1. Wouter Coekaerts - Reply

    March 19, 2010 at 9:40 am

    Thanks for writing this, it's really useful.
    We were facing the same problem and now use your PropertyPlaceholderReplacingXmlBeanFactory as a temporary workaround.

    Btw, it's fixed in Spring Security 3 according to http://jira.springframework.org/browse/SEC-1201

Add a Comment