spring

Load environment configurations and properties with Spring Example

In this example we shall show you how to load specific environment configurations and properties using Spring. Since version 3.1, Spring support an environment aware feature called profiles. Now we can activate profiles in our application, which allows us to define specific configurations beans and properties by deployment regions, such as “development”, “testing” and “production”, etc.

Let’s start our example below which shows how to use this feature where we have three environment-specific bean classes (DevEnv.java, TestEnv.java and ProdEnv.java) and properties files(application-dev.properties, application-test.properties and application-prod.properties). So, we need to load those beans and files at each environment.

1. Project Environment

  1. Spring 4.1.3
  2. Spring Test 4.1.3
  3. JUnit 4.11
  4. Apache Log4j 1.2.17
  5. Apache Maven 3.0.5
  6. JDK 1.8
  7. Eclipse 4.4 (Luna)

2. Project Structure

We create a simple Spring Maven project with the following Structure.

project-structure
Figure 1: Project Structure

3. Project Dependencies

We have the following Dependencies in our below POM file.

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.jcg.example</groupId>
	<artifactId>springproperties-example-code</artifactId>
	<packaging>jar</packaging>
	<version>1.0</version>
	<name>Spring Properties Example Code</name>

	<properties>

		<!-- Generic properties -->
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<resource.directory>src/main/resources</resource.directory>

		<!-- Spring -->
		<spring-framework.version>4.1.3.RELEASE</spring-framework.version>

		<!-- Logging -->
		<log4j.version>1.2.17</log4j.version>

		<!-- Test -->
		<junit.version>4.11</junit.version>

	</properties>

	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>

		<!-- Logging with Log4j -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j.version}</version>
		</dependency>

		<!-- Test Artifacts -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring-framework.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

	</dependencies>

	<build>

		<plugins>

			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.2</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>

		</plugins>

	</build>

</project>

4. Bean Classes

We have three simple bean classes (DevEnv.java, TestEnv.java and ProdEnv.java) which extend the GenericEnv.java interface to be able to autowire them using the GenericEnv.java interface.

GenericEnv.java:

package com.jcg.prop;

/**
 * @author ashraf
 *
 */
public interface GenericEnv {

}

DevEnv.java:

package com.env.dev;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.jcg.prop.GenericEnv;

/**
 * @author ashraf
 *
 */
@Component
public class DevEnv implements GenericEnv {
	
	private String envName = "dev";
	
	@Value("${profile.name}")
	private String profileName;

	public String getEnvName() {
		return envName;
	}

	public void setEnvName(String envName) {
		this.envName = envName;
	}

	public String getProfileName() {
		return profileName;
	}

	public void setProfileName(String profileName) {
		this.profileName = profileName;
	}

	@Override
	public String toString() {
		return "DevEnv [envName=" + envName + ", profileName=" + profileName
				+ "]";
	}

}

TestEnv.java:

package com.env.test;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.jcg.prop.GenericEnv;

/**
 * @author ashraf
 *
 */
@Component
public class TestEnv implements GenericEnv {

	private String envName = "test";

	@Value("${profile.name}")
	private String profileName;

	public String getEnvName() {
		return envName;
	}

	public void setEnvName(String envName) {
		this.envName = envName;
	}

	public String getProfileName() {
		return profileName;
	}

	public void setProfileName(String profileName) {
		this.profileName = profileName;
	}

	@Override
	public String toString() {
		return "TestEnv [envName=" + envName + ", profileName=" + profileName
				+ "]";
	}
	
}

ProdEnv.java:

package com.env.prod;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.jcg.prop.GenericEnv;

/**
 * @author ashraf
 *
 */
@Component
public class ProdEnv implements GenericEnv {

	private String envName = "prod";

	@Value("${profile.name}")
	private String profileName;

	public String getEnvName() {
		return envName;
	}

	public void setEnvName(String envName) {
		this.envName = envName;
	}

	public String getProfileName() {
		return profileName;
	}

	public void setProfileName(String profileName) {
		this.profileName = profileName;
	}

	@Override
	public String toString() {
		return "ProdEnv [envName=" + envName + ", profileName=" + profileName
				+ "]";
	}

}

5. Properties Files

We have three simple properties files(application-dev.properties, application-test.properties and application-prod.properties). Also, we have a default one application-default.properties which has the default values for specific property where it will be overridden by the specific-environment file value if it’s exist.

application-dev.properties:

profile.name=dev.profiles

# Database Properties
db.driverClass=com.mysql.jdbc.Driver
db.connectionURL=jdbc:mysql://localhost:3306/emp
db.username=dev_usr
db.password=dev_pss

# JMS Properties
jms.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
jms.provider.url=tcp://localhost:61616
jms.queue=dev.queue

application-test.properties:

profile.name=test.profiles

# Database Properties
db.driverClass=com.mysql.jdbc.Driver
db.connectionURL=jdbc:mysql://192.168.1.2:3306/emp
db.username=test_usr
db.password=test_pss

# JMS Properties
jms.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
jms.provider.url=tcp://192.168.1.2:61616
jms.queue=test.queue

application-prod.properties:

profile.name=prod.profiles

# Database Properties
db.driverClass=com.mysql.jdbc.Driver
db.connectionURL=jdbc:mysql://192.168.1.1:3306/emp
db.username=prod_usr
db.password=prod_pss

# JMS Properties
jms.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
jms.provider.url=tcp://192.168.1.1:61616
jms.queue=prod.queue

application-default.properties:

# Application Common Properties
profile.name=spring.profile

6. Spring Profiles and XML Configuration:

Spring introduces the new profile attribute to the beans element of the spring-beans schema:

  <beans profile="dev">
	<!-- Set the development environment configuration here-->
  </beans>

This profile attribute that acts as a switch when enabling and disabling profiles in different environments.

To explain all this further, we are going to make our application to load a bean class and properties file depending upon the environment on which your program is running.

So, we define the following XML configuration file:

xml-config-context.xml:

<?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: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://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd 
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- scans for annotated classes in the com.company package -->
	<context:component-scan base-package="com.jcg" />

	<!-- enables annotation based configuration -->
	<context:annotation-config />

	<beans profile="dev">
		<!-- allows for ${} replacement in the spring xml configuration from the 
			application-default.properties, application-dev files on the classpath -->
		<context:property-placeholder
			location="classpath:properties/application-default.properties, classpath:properties/application-dev.properties"
			ignore-unresolvable="true" />

		<!-- scans for annotated classes in the com.env.dev package -->
		<context:component-scan base-package="com.env.dev" />
	</beans>

	<beans profile="test">
		<!-- allows for ${} replacement in the spring xml configuration from the 
			application-default.properties, application-test files on the classpath -->
		<context:property-placeholder
			location="classpath:properties/application-default.properties, classpath:properties/application-test.properties"
			ignore-unresolvable="true" />

		<!-- scans for annotated classes in the com.env.test package -->
		<context:component-scan base-package="com.env.test" />
	</beans>

	<beans profile="prod">
		<!-- allows for ${} replacement in the spring xml configuration from the 
			application-default.properties, application-prod files on the classpath -->
		<context:property-placeholder
			location="classpath:properties/application-default.properties, classpath:properties/application-prod.properties"
			ignore-unresolvable="true" />

		<!-- scans for annotated classes in the com.env.prod package -->
		<context:component-scan base-package="com.env.prod" />
	</beans>

</beans>

As you can see, we create three profiles (dev, test and prod), each profile has the following:

Also, as you can notice that our properties files contains different properties type such Database, JMS, , etc. So, for more organized properties management, we wrapped each type by a wrapper bean where all database properties were wrapped in the DatabaseProperties.java and all JMS properties were wrapped in the JmsProperties.java as well, that will lead to more clean and maintainable code where we will get the property value through the getters method and not by the property name. So, we can do any change in the property name in its wrapper class without breaking the code which consumes the changed property.

Also, you will notice the usage of @Value annotation where we using each property key to get the value from properties file in each wrapper class.

DatabaseProperties.java:

package com.jcg.prop;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author ashraf
 *
 */
@Component
public class DatabaseProperties {

	@Value("${db.driverClass}")
	private String driverClass;

	@Value("${db.connectionURL}")
	private String connectionURL;

	@Value("${db.username}")
	private String username;

	@Value("${db.password}")
	private String password;

	public String getDriverClass() {
		return driverClass;
	}

	public String getConnectionURL() {
		return connectionURL;
	}

	public String getUsername() {
		return username;
	}

	public String getPassword() {
		return password;
	}

	@Override
	public String toString() {
		return "DatabaseProperties [driverClass=" + driverClass
				+ ", connectionURL=" + connectionURL + ", username=" + username
				+ ", password=" + password + "]";
	}

}

JmsProperties.java:

package com.jcg.prop;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author ashraf
 *
 */
@Component
public class JmsProperties {

	@Value("${jms.factory.initial}")
	private String factoryInitial;

	@Value("${jms.provider.url}")
	private String providerUrl;

	@Value("${jms.queue}")
	private String queue;

	public String getFactoryInitial() {
		return factoryInitial;
	}

	public String getProviderUrl() {
		return providerUrl;
	}

	public String getQueue() {
		return queue;
	}

	@Override
	public String toString() {
		return "JmsProperties [factoryInitial=" + factoryInitial
				+ ", providerUrl=" + providerUrl + ", queue=" + queue + "]";
	}
	
}

Now, it’s the time for testing the previous code. let’s running our test class SpringPropertiesTest.java and see the output.

As you can see, we activate our dev profile using the @ActiveProfiles(profiles = "dev") annotation, then we load our xml-config-context.xml using the @ContextConfiguration("classpath:spring/xml-config-context.xml"), after that we run SpringPropertiesTest.java test class using Eclipse through Run As and select JUnit Test.

Also, there is another way to run the unit test with a specific profile by running the following command line from inside the project directory where we activate any profile through binding spring.profiles.active value using Maven but you should firstly comment the @ActiveProfiles(profiles = "profile_name") annotation in the SpringPropertiesTest.java class before running it.

mvn clean install -Dspring.profiles.active="dev"

SpringPropertiesTest.java:

package com.jcg.test;

import junit.framework.TestCase;

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 com.jcg.prop.DatabaseProperties;
import com.jcg.prop.GenericEnv;
import com.jcg.prop.JmsProperties;

/**
 * @author ashraf
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
//Change it to your desired profile
@ActiveProfiles(profiles = "dev")
@ContextConfiguration("classpath:spring/xml-config-context.xml")
public class SpringPropertiesTest extends TestCase {
	
	@Autowired
	private GenericEnv env;
	
	@Autowired
	private DatabaseProperties dbProp;
	
	@Autowired
	private JmsProperties jmsProp;
	
	@Test
	public void testAppProperties() {
		
		System.out.println("Running DatabasePropertiesTest ...");
		
		System.out.println("Environment        : " + env.toString());
		
		System.out.println("Database Properties: " + dbProp.toString());
		
		System.out.println("JMS Properties     : " + jmsProp.toString());
	
	}
	
}

Output:
Let’s see the output below When we activate the dev profile.

Running DatabasePropertiesTest ...
Environment        : DevEnv [envName=dev, profileName=dev.profiles]
Database Properties: DatabaseProperties [driverClass=com.mysql.jdbc.Driver, connectionURL=jdbc:mysql://localhost:3306/emp, username=dev_usr, password=dev_pss]
JMS Properties     : JmsProperties [factoryInitial=org.apache.activemq.jndi.ActiveMQInitialContextFactory, providerUrl=tcp://localhost:61616, queue=dev.queue]

Also, Let’s see the output below When we activate the test profile.

Running DatabasePropertiesTest ...
Environment        : TestEnv [envName=test, profileName=test.profiles]
Database Properties: DatabaseProperties [driverClass=com.mysql.jdbc.Driver, connectionURL=jdbc:mysql://192.168.1.2:3306/emp, username=test_usr, password=test_pss]
JMS Properties     : JmsProperties [factoryInitial=org.apache.activemq.jndi.ActiveMQInitialContextFactory, providerUrl=tcp://192.168.1.2:61616, queue=test.queue]

7. Minimal XML Configuration:

As you notice that we can activate any profile through binding spring.profiles.active value using Maven, then we can read the spring.profiles.active value as a system variable using ${spring.profiles.active} in your Spring XML configuration file. Using this way, we can minimize the xml-config-context.xml size to get a version as below:

mini-xml-config-context.xml:

<?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: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://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd 
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- scans for annotated classes in the com.company package -->
	<context:component-scan base-package="com.jcg" />
	
	<!-- scans for annotated classes in the com.company package -->
	<context:component-scan base-package="com.env.${spring.profiles.active}" />

	<!-- enables annotation based configuration -->
	<context:annotation-config />

	<context:property-placeholder
		location="classpath:properties/application-default.properties, classpath:properties/application-${spring.profiles.active}.properties"
		ignore-unresolvable="true" />

</beans>

As we can see, we are using the active profile value using the placeholder ${spring.profiles.active} instead of creating three different profiles for each environment. So, now we are ended up using only one PropertySourcesPlaceholderConfigurer and ComponentScan.

Tip
Using this way, you will only be able to run the JUnit test classes through Maven where the only way for binding the spring.profiles.active value is through running the following Maven command in your CLI.

mvn clean install -Dspring.profiles.active="profile_name". 

We create another JUint test class MiniConfigSpringPropertiesTest.java where we can test the new Spring XML configuration file minimized version mini-xml-config-context.xml.

MiniConfigSpringPropertiesTest.java:

package com.jcg.test;

import junit.framework.TestCase;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.jcg.prop.DatabaseProperties;
import com.jcg.prop.GenericEnv;
import com.jcg.prop.JmsProperties;

/**
 * @author ashraf
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/mini-xml-config-context.xml")
public class MiniConfigSpringPropertiesTest extends TestCase {
	
	@Autowired
	private GenericEnv env;
	
	@Autowired
	private DatabaseProperties dbProp;
	
	@Autowired
	private JmsProperties jmsProp;
	
	@Test
	public void testAppProperties() {
		
		System.out.println("Running MiniConfigSpringPropertiesTest ...");
		
		System.out.println("Environment        : " + env.toString());
		
		System.out.println("Database Properties: " + dbProp.toString());
		
		System.out.println("JMS Properties     : " + jmsProp.toString());
	
	}
	
}

As you will notice below, we got the same output from the two JUnit test calsses when running the test.

Output:

cli-output
Figure 2: CLI Output

Download the Source Code of this example

This was an example on how to load environment configurations and properties with Spring.

Download
You can download the full source code of this example here: SpringPropertiesExample Code.zip

Ashraf Sarhan

Ashraf Sarhan is a passionate software engineer, an open source enthusiast, has a Bsc. degree in Computer and Information Systems from Alexandria University. He is experienced in building large, scalable and distributed enterprise applications/service in multiple domains. He also has a keen interest in JavaEE, SOA, Agile and Big Data technologies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Bender
3 years ago

Very helpful and interesting article thanks ! Just a few things: -2020 –> using Springboot 2.4.1 the delimiter for replacing values has been changed (somewhat not everywhere but at least for the application.properties) from ${property} to @property@ (you will require the maven-resource-plugin for that) -profiles can also be put in the application.properties directly the delimiter #— or — will separate them so you can set the the same values multiple times (for different profiles) (although very convenient thats certainly not good practice since this file might get shared somehow commits etc. but for a quick test this is very good)… Read more »

Back to top button