Spring 3.1 Environment Profiles
Profiles
Spring 3.1 now includes support for the long awaited environment aware feature called profiles. Now we can activate profiles in our application, which allows us to define beans by deployment regions, such as "dev", "qa", "production", "cloud", etc.We also can use this feature for other purposes: defining profiles for performance testing scenarios such as "cached" or "lazyload".
Essential Tokens
Spring profiles are enabled using the case insensitive tokensspring.profiles.active
or spring_profiles_active
.This token can be set as:
- an Environment Variable
- a JVM Property
- Web Parameter
- Programmatic
spring.profiles.default
, which can be used to set the default profile(s) if none are specified with spring.profiles.active
.Grouping Beans by Profile
Spring 3.1 provides nested bean definitions, providing the ability to define beans for various environments:<beans profiles="dev,qa"> <bean id="dataSource" class="..."/> <bean id="messagingProvider" class="..."/> </beans>Nested
<beans>
must appear last in the file. Beans that are used in all profiles are declared in the outer
<beans>
as we always have, such as Service classes. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="businessService" class="com.c...s.springthreeone.business.SimpleBusinessServiceImpl"/> <beans profile="dev,qa"> <bean id="constructorBean" class="com.chariotsolutions.springthreeone.SimpleBean" c:myString="Constructor Set"/> <bean id="setterBean" class="com.chariotsolutions.springthreeone.SimpleBean"> <property name="myString" value="Setter Set"/> </bean> </beans> <beans profile="prod"> <bean id="setterBean" class="com.chariotsolutions.springthreeone.SimpleBean"> <property name="myString" value="Setter Set - in Production YO!"/> </bean> </beans> </beans>
If we put a single
<bean>
declaration at below any nested <beans>
tags we will get the exception org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'bean'
.Multiple beans can now share the same XML "id"
In a typical scenario, we would want the DataSource bean to be called
dataSource
in both all profiles. Spring now allow us to create multiple beans within an XML file with the same ID providing they are defined in different <beans>
sets. In other words, ID uniqueness is only enforced within each <beans>
set.Automatic Profile Discovery (Programmatic)
We can configure a class to set our profile(s) during application startup by implementing the appropriate interface. For example, we may configure an application to set different profiles based on where the application is deployed - in CloudFoundry or running as a local web application. In theweb.xml
file we can include an Servlet context parameter, contextInitializerClasses,
to bootstrap this class:<context-param> <param-name>contextInitializerClasses</param-name> <param-value>com.chariotsolutions.springthreeone.services.CloudApplicationContextInitializer</param-value> </context-param>
The Initializer class
package com.chariotsolutions.springthreeone.services; import org.cloudfoundry.runtime.env.CloudEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; public class CloudApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { private static final Logger logger = LoggerFactory .getLogger(CloudApplicationContextInitializer.class); @Override public void initialize(ConfigurableApplicationContext applicationContext) { CloudEnvironment env = new CloudEnvironment(); if (env.getInstanceInfo() != null) { logger.info("Application running in cloud. API '{}'", env.getCloudApiUri()); applicationContext.getEnvironment().setActiveProfiles("cloud"); applicationContext.refresh(); } else { logger.info("Application running local"); applicationContext.getEnvironment().setActiveProfiles("dev"); } } }
Annotation Support for JavaConfig
If we are are using JavaConfig to define our beans, Spring 3.1 includes the @Profile annotation for enabling bean config files by profile(s).package com.chariotsolutions.springthreeone.configuration; import com.chariotsolutions.springthreeone.SimpleBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration @Profile("dev") public class AppConfig { @Bean public SimpleBean simpleBean() { SimpleBean simpleBean = new SimpleBean(); simpleBean.setMyString("Ripped Pants"); return simpleBean; } }
Testing with XML Configuration
With XML configuration we can simply add the annotation@ActiveProfiles
to the JUnit test class. To include multiple profiles, use the format @ActiveProfiles(profiles = {"dev", "prod"})
package com.chariotsolutions.springthreeone; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @ActiveProfiles(profiles = "dev") public class DevBeansTest { @Autowired ApplicationContext applicationContext; @Test public void testDevBeans() { SimpleBean simpleBean = applicationContext.getBean("constructorBean", SimpleBean.class); assertNotNull(simpleBean); } @Test(expected = NoSuchBeanDefinitionException.class) public void testProdBean() { SimpleBean prodBean = applicationContext.getBean("prodBean", SimpleBean.class); assertNull(prodBean); } }
Testing with JavaConfig
JavaConfig allows us to configure Spring with or without XML configuration. If we want to test beans that are defined in a Configuration class we configure our test with theloader
and classes
arguments of the @ContextConfiguration
annotation.package com.chariotsolutions.springthreeone.configuration; import com.chariotsolutions.springthreeone.SimpleBean; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import static org.junit.Assert.assertNotNull; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AppConfig.class, loader = AnnotationConfigContextLoader.class) @ActiveProfiles(profiles = "dev") public class BeanConfigTest { @Autowired SimpleBean simpleBean; @Test public void testBeanAvailablity() { assertNotNull(simpleBean); } }
Declarative Configuration in WEB.XML
If we desire to set the configuration inWEB.XML
, this can be done with parameters on ContextLoaderListener
. Application Context
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app-config.xml</param-value> </context-param> <context-param> <param-name>spring.profiles.active</param-name> <param-value>DOUBLEUPMINT</param-value> </context-param>Log Results
DEBUG PropertySourcesPropertyResolver - Found key 'spring.profiles.active' in [servletContextInitParams] with type [String] and value 'DOUBLEUPMINT'
Environment Variable/JVM Parameter
Setting an environment variable can be done with either
spring_profiles_default
or spring_profiles_active
. In Unix/Mac it would be export SPRING_PROFILES_DEFAULT=DEVELOPMENT
for my local system. We can also use the JVM "-D" parameter which also works with Maven when using Tomcat or Jetty plugins.
Note: Remember the tokens are NOT case sensitive and can use periods or underscores as separators. For Unix systems, you need to use the underscore, as above.
Logging of system level properties
DEBUG PropertySourcesPropertyResolver - Found key 'spring.profiles.default' in [systemProperties] with type [String] and value 'dev,default'
Hi Gordon, thanx for your post.
ReplyDeleteHave you compare Spring Environment profile with maven profile ?
I'm currently using maven profile to separate properties resources based on the maven provile (EG: mvn -Pprod...) and I'd like to known if It's time to move to the new spring environment abstraction
Cheers,
--
Fabio
This comment has been removed by the author.
DeleteHi Fabio,
ReplyDeleteMaven profiles would provide build time choices, where Spring Profiles would provide runtime configuration and deployment. In the case of Spring, the build could be the same for all platforms. When the application starts it could dynamically determine which bean sets to configure. Does that help?
Regards,
Gordon Dickens
I am speaking at ETE: http://phillyemergingtech.com/2012
Hi Gordon,
ReplyDeleteFirst and foremost, nice post. Keep 'em coming.
Second, some quick tips:
1) There's no need to specify the 'profiles' attribute for @ActiveProfiles unless you also want to override the 'inheritProfiles' attribute. So with that in mind, your tests could simply declare the following.
@ActiveProfiles("dev")
2) Unless you happen to have some XML files lying around in the classpath that meet the naming requirements for a default XML configuration file, there's no need to specify the AnnotationConfigContextLoader for tests that use JavaConfig. For example, in your example you could just declare the following.
@ContextConfiguration(classes = AppConfig.class)
Cheers,
Sam Brannen (author of the Spring TestContext Framework)
It's worth to mention that if you have a 3.0.x web app using spring and u want to use this feature, u must, beside changing your deps, add the spring-web module. If you don't, the system will not look into the [servletContextInitParams] PropertySource
ReplyDeleteTo use profiles with environment variables, you should set the default profile in the web.xml context:
ReplyDeletespring.profiles.default
DOUBLEUPMINT
Then you can override this by setting env variable or system param.
If setting the active profile in the web.xml, then it cant be overridden by env vars/system props.