Thursday, July 2, 2015

How to set development/test/production environment in spring project (Java Config)

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:

  1. Finally there will be only one packaged jar for all different environment
  2. The same jar can run in development environment, by giving option  "-Dspring.profiles.active=development" to JVM
  3. The same jar can run in test environment by giving option  "-Dspring.profiles.active=test" to JVM
  4. The same jar can run in production environment without options to JVM
  5. 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 Java config. For tutorial using spring XML 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+
  • 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-java</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-use-profiles-java</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 3 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.annotation.AnnotationConfigApplicationContext;

public class DemoMain {

public static void main(String[] args) {
AnnotationConfigApplicationContext ctx= new AnnotationConfigApplicationContext(DemoJavaConfig.class);

HelloWorldBean helloWorld = ctx.getBean(HelloWorldBean.class);
System.out.println(helloWorld.sayHello());
}
}



The main class creates the spring context from a Java class called DemoJavaConfig, which is the spring configuration, then just use spring context to get a bean and call the method of the bean, here is the HellowWorldBean. The third class, DemoJavaConfig, is the key of this article.

package com.shengwang.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

@Configuration
public class DemoJavaConfig {

@Value("${hellow.world.name}") String helloWorldName;

@Bean
public HelloWorldBean helloWorldBean() {
return new HelloWorldBean(helloWorldName);
}

/**
* Add PropertySourcesPlaceholderConfigurer to make placeholder work.
* This method MUST be static
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
Resource resource;
String activeProfile;

PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();

// get active profile
activeProfile = System.getProperty("spring.profiles.active");

// choose different property files for different active profile
if ("development".equals(activeProfile)) {
resource = new ClassPathResource("/META-INF/development.properties");
} else if ("test".equals(activeProfile)) {
resource = new ClassPathResource("/META-INF/test.properties");
} else {
resource = new ClassPathResource("/META-INF/production.properties");
}

// load the property file
propertySourcesPlaceholderConfigurer.setLocation(resource);

return propertySourcesPlaceholderConfigurer;
}
}



As a spring configuration class, class DemoJavaConfig has annotation @Configuraton. Inside the class definition, there is a field variable helloWorldName. Annotation @Value is used to get value from the property files. Then use it as constructor argument to init the HelloWorldBean. To make the property files work, a static bean must be added to return a PropertySourcesPlaceholderConfigurer. What's in the static method propertyConfigurer is create PropertySourcesPlaceholderConfigurer with only ONE property file, according to active profile to determine which property file will be used. 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. development.properties:

# properties for development environment

# jdbc properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=simplePassword

# hello world property
hellow.world.name=development environment



production.properties:

# properties for production environment

# jdbc properties
jdbc.driver=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@192.168.203.95:1521:sip
jdbc.username=userABC
jdbc.password=complexPassword

# hello world property
hellow.world.name=production environment



Now the whole project directory hierarchy looks like.




image




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.

image


Run the project and we can see it's running in the development environment.




image




6. Specify profile for Junit




On thing to notice is that the annotation @AcctiveProfiles does NOT work, we need to set the system property before spring context initialized.

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(classes = DemoJavaConfig.class)
//@ActiveProfiles("development") // Does NOT work
public class HelloWorldBeanTest {

@Autowired HelloWorldBean helloWorld;

public HelloWorldBeanTest () {
// set active profile manually in constructor
System.setProperty("spring.profiles.active","development");
}

@Test public void testSayHello() {
assertEquals("Hello development environment",helloWorld.sayHello());
}
}



Hope this article can make your life easier!

1 comment:

  1. That was pretty nice buddy! Thx for that, much nicer than using the Environment annotation.

    ReplyDelete

Powered by Blogger.

About The Author

My Photo

Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List