(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