tastybyte

Tuesday, May 29, 2007

Environment-specific config with Spring

The Problem


After a jaunt working with Rails I'm back in Java-land. Now that I've tasted the sweet simplicity I'm jonesin' to feed it back into my Java projects.

So here's a little hack that let me apply my Rails idiom of environment/[development|test|production] to the Java world.

The Implementation

The config files

development.properties:
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@...

hibernate.dialect=org.hibernate.dialect.OracleDialect
...

test.properties:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:mem:unittest
jdbc.username=sa
jdbc.password=

hibernate.dialect=org.hibernate.dialect.HSQLDialect
Same deal with production.properties

Creating the ApplicationContext

It's not a web-app so I'm doing it programatically.
beanFactory = new ClassPathXmlApplicationContext(new String[] {"beans.xml"}, false);

PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE);

String environment = System.getProperty("environment", "development");
File customProperties = new File("/etc/conf/custom.properties");
configurer.setLocations(new Resource[] {new ClassPathResource(environment + ".properties"), new FileSystemResource(customProperties)});

beanFactory.addBeanFactoryPostProcessor(configurer);
beanFactory.refresh();

Unit tests

I also created a mini spring file beans-test.xml:
<beans>
<bean class=\"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer\">
<property name=\"locations\">
<value>classpath:test.properties</value>
</property>
</bean>
</beans>
All it does is load in my test.properties.
My test superclass
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;

public abstract class TestSupport extends AbstractDependencyInjectionSpringContextTests {
public AbstractMdsIntegrationTestSupport() {
setPopulateProtectedVariables(true);
}

@Override
protected String[] getConfigLocations() {
return new String[] {"classpath:/beans-test.xml", "classpath:/beans.xml"};
}
}

Results

Now I can check out a clean codebase and run the unit tests and also fire up my application with the main() function, both from my IDE with no flag, properties file editing, etc. Then I can switch to production mode with just a -Denvironment=production.

You may have caught the customProperties file, that's how I keep from having to wrangle my production passwords into the classpath or system properties.