Music, software, life⦠and stuff.
[ Twitter ] [ GitHub ] [ Linked In ]
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.
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.
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.
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
}
}
}
}
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 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