Wednesday, January 4, 2012

Spring bean definition profiles and web.xml

Spring 3.1 was released a few weeks ago. One of the interesting new features is the first-class support for bean definition profiles: you can define beans as part of a profile and than activate that profile if required. Bean definitions that are part of a profile that has not been activated are simply skipped.

Last week, I spent a few days upgrading our application to Spring 3.1 and updating it to make use of bean definition profiles. As most other sizable Spring based apps, we had devised our own configuration switching system based on a combination of Maven resource filtering and XML <import/> elements containing ${placeholders}. This works well enough but it would obviously be better to directly use the bean definition profile feature of Spring 3.1, if only because it's better documented.

By default, the list of active bean definition profiles will be determined by looking at a spring.profiles.active property. Out-of-the-box, a Spring WebApplicationContext will setup a PropertySources list containing the servlet config parameters, servlet context parameters, JNDI properties, system properties and environment variables, in that order. This implies that you can define the spring.profiles.active property in any of those locations, the most obvious one being servlet context parameters in web.xml:
<context-param>
 <param-name>spring.profiles.active</param-name>
 <param-value>foo-profile, bar-profile</param-value>
</context-param>
We wanted to use Maven profiles to switch between Spring bean definition profiles, so the initial idea was to simply do Maven resource filtering on web.xml:
<context-param>
 <param-name>spring.profiles.active</param-name>
 <param-value>${spring.profiles.active}</param-value>
</context-param>
It turns out doing resource filtering on web.xml leads you to a world of pain:
  • The Jetty plugin for Maven assumes the web.xml is static and simply uses the one in the source folder. You can explicitly tell it to use the web.xml from the target folder (using the <webXml> config element), but that results in other problem: by default resource filtering does not process the src/main/webapp directory. Of course you can specify filtering for that directory in your POM, but that again given problems: the Maven war plugin will overwrite the filtered web.xml with a copy from the source folder while packaging the web application. You can fix this by adding a <webResources> definition to your POM for the war plugin. Clearly this situation is far from ideal: it's complex and requires an explicit mvn package just to be able to do mvn jetty:run.
  • Idem ditto for Tomcat.
  • Eclipse WTP also directly uses the web.xml from the sources folder. Maybe there are ways around this, but I have no idea how that would work.

In the end we decided to forgo Maven resource filtering on web.xml and simply use an application specific properties file:
spring.profiles.active=${spring.profiles.active}
To get Spring to pick up this additional properties file, we simply add a property source to the application context using an ApplicationContextInitializer:
public class MyAppCtxInitializer
  implements ApplicationContextInitializer<ConfigurableApplicationContext> {
 public void initialize(ConfigurableApplicationContext applicationContext) {
  try {
   applicationContext.getEnvironment().getPropertySources()
     .addLast(new ResourcePropertySource("classpath:/myapp.properties"));
  } catch (IOException e) {
   // properties file cannot be found: ignore and just continue without these properties
   logger.info("Unable to load fallback properties: " + e.getMessage());
  }
 }
}

This approach avoids all the downsides of doing filtering on web.xml (web.xml is now completely static and mvn package no longer required). The only negative aspect is that it introduced an application specific properties file instead of simply relying on standard Spring conventions.

No comments:

Post a Comment