Spring offers a couple of configurer objects that allows for configuration of bean properties with Java property files. My goal is to implement a configurer that uses ConfigSlurper scripts instead. As an example (from the Spring reference documentation), using PropertyPlaceholderConfigurer:
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <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>Here's the associated property file:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql:production:9002 jdbc.username=sa jdbc.password=rootWith ConfigSlurper we could write something like this:
def normalize(s){ return s.toUpperCase() } jdbc{ driverClassName=org.hsqldb.jdbcDriver url=jdbc:hsqldb:hsql:production:9002 username=normalize('sa') password=normalize('root') }Notice that in addition to the usage of scoping to avoid repetition, we can employ basically any Groovy construct inside the script. Needless to say, this example aims to show some of the possibilities, not to represent a relevant use case.
Integration with Spring
The integration is surprisingly simple thanks to the flexibility of both Spring and Groovy. ConfigSlurper parses a script and produces a ConfigObject. A ConfigObject can be merged with another ConfigObject (coming from another script) and finally transformed to a java.util.Properties.As a base for our GroovyPlaceholderConfigurer, I'm using PropertyPlaceholderConfigurer. Thus, the usual behaviour of property parsing and resolution can be preserved. We only need to redefine the loadProperties method in order to plug the ConfigSlurper in. Here's the code:
public class GroovyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { private Resource[] locations; @Override protected void loadProperties(Properties props) throws IOException { ConfigObject configObject = new ConfigObject(); ConfigSlurper configSlurper = new ConfigSlurper(); for (Resource l : locations) { configObject.merge(configSlurper.parse(l.getURL())); } props.putAll(configObject.toProperties()); } public void setLocations(Resource[] locations) { this.locations = locations; } public void setLocation(Resource location) { this.locations = new Resource[] { location }; } }To use it, we need to define a bean in the application context:
<bean class="cafebabe.GroovyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:config.groovy</value> <value>classpath:config2.groovy</value> </list> </property> </bean>That's it!