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 problems: 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.
But why didn't you just simply use the environment? -Dspring.profiles.active=xxxxx
ReplyDelete"We wanted to use Maven profiles to switch between Spring bean definition profiles."
ReplyDeleteThe reason for this is that it combines the power of the two profile systems:
* Maven profiles can control things like application dependencies (i.e. we don't want the H2 jar in our real builds)
* Spring profiles can control application beans (i.e. we want JNDI lookup in our real builds, not direct DataSource beans)