Fork me on GitHub

LD.

Music, software, life… and stuff.

[ Twitter ] [ GitHub ] [ Linked In ]

Configuration based Spring bean definition with Grails.

I released a new version of the Grails JMS plugin (0.5) over the weekend. This release features completely rewritten internals to expose every configuration aspect of the underlying Spring JMS classes. JMS is a complicated topic with lots of setup and configuration options so this level of access is necessary.

What’s novel about the approach is that it allows users to configure the underlying beans via the Grails application config, rather than via direct Spring bean configuration. This has the advantage of being more generally accessible as a lot of Grails developers still shy away from directly configuring beans. It also has the advantage of allowing the plugin to assert a certain level of control over the bean definitions.

A little bit about Grails JMS

The plugin makes it convenient to specify service methods as JMS listeners.

import grails.plugin.jms.Subscriber

class ListeningService {
    
    @Subscriber
    void interestingStuff(payload) {
        
    }
    
}

Whenever a message is sent to the topic “interestingStuff”, the message will be delivered to this service method.

There are two aspects to receiving JMS messages with Spring’s JMS support; containers and adapters. For each listener there is a distinct container and adapter pair. In a plain Spring app, you would configure a pair of these beans to receive your messages. If you have a look at those classes, there are a lot of config options. The goal of this plugin release was to give you access to those options should you need it, but not force you to define everything yourself.

The @Subscriber annotation has container and adapter parameters that default to "standard". These parameters are the names of the abstract beans that the concrete listener and adapter instances shall inherit from. There are implicit suffixes though; a container value of "standard" actually means the bean standardJmsListenerContainer and so forth. The plugin does force some properties on the concrete instances, but very few. The majority of configuration is controlled by the abstract base.

Config based bean definitions

The other half of the story is how the beans are defined based on the config. This is driven by the MapBasedBeanDefinitionBuilder that takes a name and Map of properties a certain format and registers a bean based on the map data. There are also certain MapBasedBeanDefinitionBuilder subclasses in the plugin like JmsListenerContainerAbstractBeanDefinitionBuilder that specialise in creating certain types of beans.

The default set of beans configurations look like this…

import org.springframework.jms.support.converter.SimpleMessageConverter
import org.springframework.jms.listener.DefaultMessageListenerContainer

templates {
    standard {
        connectionFactoryBean = "jmsConnectionFactory"
        messageConverter = new SimpleMessageConverter()
    }
}
containers {
    standard {
        concurrentConsumers = 1
        subscriptionDurable = false
        autoStartup = false
        connectionFactoryBean = "jmsConnectionFactory"
        messageSelector = null
        cacheLevel = DefaultMessageListenerContainer.CACHE_SESSION
    }
}
adapters {
    standard {
        messageConverter = new SimpleMessageConverter()
        persistenceInterceptorBean = 'persistenceInterceptor'
    }
}

The details aren’t important. The point is to illustrate the syntax.

Overriding defaults

This default config gets merged with the application config under the jms key (e.g. grailsApplication.config.jms). So to override the standard container to use a different connection factory we would have the following in our Config.groovy

jms {
    containers {
        standard {
            connectionFactoryBean = "someOtherJmsConnectionFactory"
        }
    }
}

Because Config.groovy is environment aware, we can also do things like

environments {
    development {
        jms.containers.standard.connectionFactoryBean = "someOtherJmsConnectionFactory"
    }
    test {
        jms.containers.standard.connectionFactoryBean = "testJmsConnectionFactory"
    }
}

You could even change the class of the default container definition.

jms {
    containers {
        standard {
            meta {
                clazz = SomeOtherListenerContainer
            }
        }
    }
}

Additional Beans

This mechanism also supports having more than one container type

jms {
    containers {
        other {
            meta {
                parentBean = "standardJmsListenerContainer"
            }
            connectionFactoryBean = "otherJmsConnectionFactory"
        }
    }
}

This creates a new container type called other that inherits all of the configuration from the standard container, but overrides the connection factory.

You would then use this container by specifying the container on the listener.

import grails.plugin.jms.Subscriber

class ListeningService {
    
    @Subscriber(container = "other")
    void interestingStuff(payload) {
        
    }
    
}

The Point

The point of this article is not specifically to talk about the JMS plugin. This approach to bean configuration seems to work quite well when users need access to the underlying beans in an optional manner. This may be an approach that other plugin authors might want to adopt.

Posted: Mar 9th, 2010 @ 10:42 am

Tags: #software  #grails  

Comments

Archive · RSS · Theme by Autumn