(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:
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.
Pingback: A Simpler Custom Property in Spring | Rob's Blog