In real life development, a project always need different sets of configurations for development, system test and production environments. Most common variables in these different environments are database location or jms location.
If the project uses spring and will not run in any container, the spring profiles can be used to make your life easier when switching environments. This article demonstrates the best practice to do so in my experiences to achieve these goals:
- Finally there will be only one packaged jar for all different environment
- The same jar can run in development environment by giving option "-Dspring.profiles.active=development" to JVM
- The same jar can run in test environment by giving option "-Dspring.profiles.active=test" to JVM
- The same jar can run in production environment without options to JVM
- The JUnit test cases can easily switch between different environments
If your project will run within web containers, should use JNDI to define the resources and inject resources into Java code.
The basic idea is put property variables in 3 different property files, namely development.properties/test.properties/production.properties, then use the proper file for different environment ( spring active profile).
This tutorial uses spring XML config. For tutorial using spring Java config, see here.
In this demo, there are 2 beans, a data source which connecting to different DB, and a hello world print out different string in different environment.
0. What you need
- JDK 1.7+
- Maven 3.2.1
- Spring 4.1.0.RELEASE
1. Maven pom.xml
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shengwang.demo</groupId>
<artifactId>spring-use-profiles-xml</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-use-profiles-xml</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<!-- Test Artifacts -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.0.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Use Java 1.7 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
This dependencies in this demo can be divided into 3 groups, Spring, Database connection and Unit test.
2. Define Java class
There are 2 classes. The first one is a simple hello world bean, which use a property value to say hello to.
package com.shengwang.demo;
import org.springframework.stereotype.Component;
@Component
public class HelloWorldBean {
String name;
public HelloWorldBean(String name) {
this.name = name;
}
public String sayHello() {
return "Hello "+name;
}
}
The second class is the main class.
package com.shengwang.demo;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public class DemoMain {
public static void main(String[] args) {
// Initial spring context with profiles
GenericXmlApplicationContext ctx= new GenericXmlApplicationContext();
ConfigurableEnvironment env = (ConfigurableEnvironment) ctx.getEnvironment();
env.setDefaultProfiles("production");
ctx.load("spring-context.xml");
ctx.refresh();
HelloWorldBean helloWorld = ctx.getBean(HelloWorldBean.class);
System.out.println(helloWorld.sayHello());
}
}
In the main, spring context is created from xml configuration file. Notice that the default active profile sets to "production", so if there is no option to set active profile when running, the program run in production environment.
3. Spring configuration
Spring configuration file spriing-context.xml, is the key of this article.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- ======================================= -->
<!-- use varibles in bean definition -->
<!-- ======================================= -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean class="com.shengwang.demo.HelloWorldBean">
<constructor-arg value="${hellow.world.name}" />
</bean>
<!-- ====================================================== -->
<!-- import different variables according to active profile -->
<!-- ====================================================== -->
<beans profile="development">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/development.properties" />
</beans>
<beans profile="test">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/test.properties" />
</beans>
<beans profile="production">
<context:property-placeholder
ignore-resource-not-found="true"
location="classpath*:/META-INF/production.properties" />
</beans>
</beans>
Beside the xml header, the spring xml configuration mainly has 2 parts. The first part define 2 beans, a dataSource and a helloworldBean. The point need to mention is the placeholders, looks like ${…}, used as values in bean definitions. Where are the placeholders defined? In the property files that are imported in the second part of the configuration. There are 3 property files represents development/test/production environments respectively.
4. Property file
Property files are plain text files. Line starts with '#' is comment. There are 3 property files: development.properties, test.properties, production.properties. Three files have same properties but with different values. Only 2 are listed here to save space.
Now the whole project directory hierarchy looks like.
5. Specify profile during development
As beginning mentions, in development, set JVM options -Dspring.profiles.active=development in your IDE or command line. For example in Eclipse, this can be done using menu Run->Run Configuration. This JVM option only need to be specified for run the project. There is no need to specify this JVM option for JUNIT test.
Run the project and we can see it's using the development properties.
6. Specify profile for Junit
Add @ActiveProfiles in the junit test to specify the profile. There is NO need to specify the "-Dspring.profiles.active" JVM option for JUNIT test.
package com.shengwang.demo;
import static org.junit.Assert.assertEquals;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-context.xml")
@ActiveProfiles("development")
public class HelloWorldBeanTest {
@Autowired HelloWorldBean helloWorld;
@Test public void testSayHello() {
assertEquals("Hello development env",helloWorld.sayHello());
}
}
Hope this article can make your life easier!
can we have 2 or more property place holders in context.xml?
ReplyDeletegood article thank you
ReplyDeletegreat article
ReplyDeleteGreat Article
ReplyDelete