DigitalJoel

2011/01/15

Simple Application Configuration With Spring

Filed under: development, java, spring — Tags: , , — digitaljoel @ 1:48 am

Inevitably it happens in every project. You’re coding along, and you have a “constant” that isn’t really constant. In my case, it was the number of minutes that should pass before a given token is invalid. 2 hours? Yeah, that sounds good… for now. But you feel all dirty if you put it right in a .java file. You could provide the value as a property to your bean if you are doing XML configuration, but is that really that much better? You want it in a properties file, because that’s one reason properties files exist, and it’s all configurable and stuff, right?

The company at which I spend my daylight hours has a ConfigurationService that takes care of hierarchical property overriding and all that. I’m sure it’s nifty. I didn’t want to write anything like that for the project on which I spend my evening/weekend hours, so I found something in spring that would do it for me and had it going pretty quickly.

Alright, here we go with the simple solution built right into Spring.

My project is a multi-module maven project with a single parent pom and each module under that parent project. I believe it’s a fairly common setup. In a module early in the dependency tree, I created a spring configuration file that looks like this.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- a bean for storing configuration properties. -->    
    <bean id="runtimeConfig" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath*:/META-INF/config/*.properties</value>
                <value>file:///${user.home}/myproject.properties</value>
            </list>
        </property>
    </bean>
    
</beans>

Yep, one bean. It creates a single java Properties object from the list of files I give it, from top to bottom, with properties in later files overriding properties in earlier files. In this case, I added the wildcard /META-INF/config/*.properties, so all of my other modules can put their default property values in properties files in META-INF/config and if they depend on this module, it’ll find them. Note that you must use classpath*: and the path must have at least one directory in it or else you will likely read from the location in only one of your jar files.

Some considerations here:

If you are using another jar, say from a third party vendor, and they have properties in META-INF/config, then you may load all those properties too. I haven’t seen it in my limited testing, but that would stink. You might be better off changing that to something like META-INF/config/myproject-*-default.properties, then you would be more sure that you will only get your properties. You don’t really want to put specific properties file names in here because you don’t want this module, which is early in the dependency tree, to know explicitly about properties files in modules it knows nothing about.

Also, depending on how your properties files within your jar files are named, your property overriding may not occur as you hoped. Hopefully, you don’t have properties with the same name in different properties files, in which case this is a non-issue. If you do have properties with the same name in different properties files in your various modules you may have to be even more careful in how they are loaded in your bean definition.

Finally, with the file:///${user.home}/myproject.properties value last, you can override any of the properties in the default configuration files with properties in a single file in your user home directory. This is pretty cool. This way your application can define the default timeout at 120 minutes but when you are testing and don’t want to wait 2 hours for a timeout you just set the value in your home directory configuration file to 1 minute and Bob’s your uncle. No risk of checking in that configuration value because it’s not under source control.

Another thing you could likely do here is to load different properties files based on some environment variable. You want to test against postgres? Set an environment property when you run your build and set the pattern to something like file:///${user.home}/${dbtype}-config.properties. Each ${dbtype}-config.properties file could give connection information for the specified database, so your home directory would have postgres-config.properties, oracle-config.properties, mysql-config.properties, etc. Then, in hudson, bamboo, or whatever you define a property for the build that has the database type in it. For maven command line, your command would be something like this. I tried it with a simple property and it worked great.

mvn -DargLine="-Ddbtype=postgres" test

Ok, moving on. Now we want to use this fancy properties object we’ve created.

So, we’ve moved down the dependency tree to a module that depends on my module with the fancy runtimeConfig bean. In my spring configuration within that module, I do the following:

<!-- all that other spring stuff you have defined -->


    <import resource="classpath:/META-INF/spring/domain-api-beans.xml"/>
    <context:property-placeholder properties-ref="runtimeConfig" />

    <bean id="yourDataSource" 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>

So, we import our previous spring configuration file so we can use the awesome runtimeConfig bean. Next, we create a property-placeholder so we can reference the properties in our runtimeConfig within this very same configuration file. Then, magically, all of the ${…} values in our datasource definition are replaced by the values in our properties files.

“BUT WAIT!” You say, “This isn’t the use case you mentioned at the top of the post. I can do this by specifying the locations of the property-placeholder.” Yes. Yes you can. This was just extra credit to show that we don’t have to point to the same locations in two definitions. Just once will do.

So, now we have a service or some bean or something, and we want to get our expiration configuration value out of it. How would we accomplish such a thing? Funnily enough, that’s the easiest part of all this.


    // all my service bean code or whatever

    // here we read the value from the runtimeConfig bean using Spring's expression language.
    @Value("#{runtimeConfig['account.validation.expiration.minutes']}" )
    private long minutes;

    // here's a nice method to return the value so I don't have that ugly @Value thing everywhere I want to reference the value.
    public long getValidityMinutes()
    {
        return minutes;
    }
    
    // the rest of my service bean code and stuff

Voila, I’ve successfully read the property from the file in my user home, and if it’s not set there, then I’ve read it from the reasonable defaults set within the properties file in the jar that uses the value. I didn’t have to write any service, bean, or anything to implement a hierarchical properties based configuration system for my application.

Update: One thing I failed to mention, which you probably already figured out, is that since the runtimeConfig is just a bean, if you know that you are going to be reading a ton of properties out of it, you could just inject the entire bean, something like this.

@Resource( "runtimeConfig" )
private Properties config;
Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.