Saturday, October 11, 2014

Spring 4 modular @Enable* annotation

I had a project that included several different deployments that uses similar architecture: all of them are Java EE Spring 4 based, deployed on Amazon Beanstalk environment, and share lots of cross-concern code, such as logging infrastructure, security mechanism, EC2-related custom code, etc.

The natural solution for such environment is to have a common infrastructure jar that will hold the common logic, and each project would be able set different configuration, if needed.

One instance is the cross-concern requirement to add a Log4J SocketAppender, in order to hook the projects into a central logging server that uses the Kibana+Logstash+Elasticsearch stack. Each project might use a different SocketAppender setting (different host, different port, different application name), but the wiring is the same. Let's examine the code that would do it.

First, an annotation should be defined, to enable the Logstash wiring:

The important bit here is the @Import(LogstashConfiguration.class) part. This allow spring to "understand" it needs to import the configuration bean LogstashConfiguration to its context. This will bootstrap the wiring.

An object that will hold the config data need to be created:

An interface to allow the projects to hook a configuration listener need to be defined:

Adding an concrete implementation of this interface in the target project will allow to configure the logstash binding. For instance, in order to set the hostname from the environment variable:

Now, we can define the actual binding logic:

This object is invoked due to the @Import code in the @EnableLogstash annotation. It has its own @ComponentScan annotation, to make Spring scan the relevant classes as well. It implements the ApplicationListener interface, in order to bind the SocketAppender during the context refresh event.

Now you can annotate the application with @EnableLogstash in order to activate the Logstash asynchronious appender, and use LogstashConfigurer implementation to pass configuration - whether you store it in property files, environment variables, system properties, or whatever.