Rob Gordon is a seasoned Java developer and a big fan of open source. Oddjob is his own open source project to make job scheduling and task automation in Java much much easier. Rob is based in London. Rob is a DZone MVB and is not an employee of DZone and has posted 17 posts at DZone. You can read more from them at their website. View Full User Profile

A Custom Property in Spring

04.21.2012
| 6165 views |
  • submit to reddit

<context:property-placeholder> is a really easy way to provide property replacements in Spring configurations with values from a standard Java Properties file. But what if you don’t want a property hard coded into a file – a clear text password for instance? Spring provides all the bits and pieces to write your own property replacement. Let me introduce my CustomPropertyConfigurer.

I’ll demonstrate using a variation on the theme of the Spring JDBC Template. MyQuery is a simple extension of org.springframework.jdbc.core.JDBCTemplate that gets the current timestamp from a MySQL database. Here’s the, hopefully familiar, configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 
  <context:property-placeholder location="classpath:jdbc.properties" />
 
  <bean id="myQuery" class="rob.MyQuery">
    <property name="dataSource" ref="dataSource" />
  </bean>
 
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
  </bean>
 
</beans>

Except the jdbc.properties file does not contain the password:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://rob-7
jdbc.username=rob

I will set the jdbc.password property myself from what is entered on the command line.

public static void main(String... args) {
 
    char[] password = System.console().readPassword("Password: ");
 
    Properties properties = new Properties();
    properties.setProperty("jdbc.password", new String(password));
 
    ConfigurableApplicationContext context =
        new ClassPathXmlApplicationContext(
            new String[] {
                "rob/MyQuery.xml"},
            false);
 
    context.addBeanFactoryPostProcessor(
            new CustomPropertyConfigurer(properties));
    context.refresh();
 
    MyQuery myQuery = context.getBean(MyQuery.class);
 
    myQuery.run();
 
    context.close();
}

Where the CustomPropertyConfigurer is:

import java.util.Properties;
 
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringValueResolver;
 
public class CustomPropertyConfigurer implements BeanFactoryPostProcessor {
 
    private final Properties properties;
 
    public CustomPropertyConfigurer(Properties properties) {
        this.properties = properties;
    }
 
    public void postProcessBeanFactory(
            ConfigurableListableBeanFactory beanFactoryToProcess)
    throws BeansException {
 
        BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(
                new BeanDirectoryResolver());
 
        String[] beanNames =
                beanFactoryToProcess.getBeanDefinitionNames();
        for (int i = 0; i < beanNames.length; i++) {
 
            BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(
                    beanNames[i]);
            try {
                visitor.visitBeanDefinition(bd);
            }
            catch (BeanDefinitionStoreException ex) {
                throw new BeanDefinitionStoreException(
                        bd.getResourceDescription(), beanNames[i],
                        ex.getMessage());
            }
        }
    }
 
    class BeanDirectoryResolver implements StringValueResolver {
 
        private final PropertyPlaceholderHelper helper;
 
        public BeanDirectoryResolver() {
            helper = new PropertyPlaceholderHelper("${", "}");
        }
 
        public String resolveStringValue(String strVal) {
            return helper.replacePlaceholders(strVal, properties);
        }
    }
}

The CustomPropertyConfigurer gets applied first. It leaves any properties it can’t resolve (all but the password) for the standard <context:property-configurer> to resolve. Unit tests running against a different jdbc.propeties file can continue to provide the password as before.

Here it is running:

Cusom Propery Configurer Running

There are many other examples of configuration values that might only be discovered at runtime – file names, schedule dates, form values etc. So long as the value can be a String, a CustomPropertyConfigurer provides a simple way of passing these values to Spring.

 

Published at DZone with permission of Rob Gordon, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Greg Allen replied on Sun, 2012/04/22 - 5:03pm

Interesting to see the full source of a custom configurer, but I think the use-case is far too trivial to justify so much custom code. I don't fancy writing a meaningful unittest for the class.

Instead, surely you could just add a vanilla PropertySourcesPlaceholderConfigurer in the above example for the custom class?

Note that you can use setOrder() and setIgnoreUnresolvablePlaceholders() to ensure that multiple configurers play nicely together.

Rob Gordon replied on Mon, 2012/04/23 - 2:48am in response to: Greg Allen

Hi Greg - you are completely right. Lines 14 to 15 of the main method could be replaced with:

 	PropertySourcesPlaceholderConfigurer configurer =
new PropertySourcesPlaceholderConfigurer();
configurer.setProperties(properties);
configurer.setIgnoreUnresolvablePlaceholders(true);

context.addBeanFactoryPostProcessor(configurer);

The problem with developing pet projects in isolation is that it is easy to miss the obvious solution. My ultimate goal is to retrieve the properties from Oddjob, for which I think I will still need a version of my CustomPropertyConfigurer - but I will update my blog so that the example is more applicable to the solution.

Thanks for the feedback.

ramesh kesavana... replied on Thu, 2013/04/18 - 7:11pm

I am trying to supply a runtime name of the file (my message queue.properties file differs for my different environment)<bean id="localhostInetAddress" class="java.net.InetAddress"factory-method="getLocalHost" /><bean id="hostname" factory-bean="localhostInetAddress"factory-method="getHostName" /><context:property-placeholder location="classpath:msgqueue-${hostname}.properties" />But I am not able to set the $hostname} with the name of my hostname for spring application context to load it dynamically.If you have pointers for the same that would be really helpful

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.