A Custom Property in Spring

(Please note there as a postscript to this post: A Simpler Custom Property in Spring)

<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 RunningThere 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.

One thought on “A Custom Property in Spring

  1. Pingback: A Simpler Custom Property in Spring | Rob's Blog