spring

Loading environment specific configurations and properties with Spring using Maven Profiles and Settings Example

In this example we shall show you how to load specific environment configurations and properties with Spring using Maven POM Profiles and XML SettingsOur previous example, shows how to load specific environment configurations and properties using Spring where these properties files are located inside the project resource folder.

However, sometimes we have a sensitive authentication information like database user name, passwords, etc., and uploading such piece of information on version control system (Git, SVN) may not be allowed. So, using Maven settings.xml file as an external properties values holder outside our project directory is a good approach where it will not be bundled with the project.

 
When Maven loads the project’s POM, it will pickup the given activated profile from the settings.xml file, and inject the properties declared within the profile in the corresponding POM profile.

Now, It’s the time for Spring magic which supports using PropertySourcesPlaceholderConfigurer configured with its environment-specific properties file. Now we can activate the desired environment Maven profile while we are building our application, that allows to load 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 two environment-specific properties (Database, JMS) where each environment has different values for these properties. So, we need to load these properties at each environment.

1. Project Environment

  1. Spring 4.1.5
  2. Spring Test 4.1.5
  3. JUnit 4.12
  4. Apache Maven 3.0.5
  5. JDK 1.8
  6. 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. Maven Profiles with Settings.xml

We have the following three Maven profiles (dev, test and prod) inside 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>springmavenproperties-example-code</artifactId>
	<packaging>jar</packaging>
	<version>1.0</version>
	<name>Spring Maven 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.5.RELEASE</spring-framework.version>

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

		<!-- Test -->
		<junit.version>4.12</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>

	<profiles>
		<profile>
			<id>dev</id>
			<!-- Dev Env. Properties -->
			<properties>
				<profile.name>${profile.name}</profile.name>
				<!-- Database Properties -->
				<db.driverClass>${db.driverClass}</db.driverClass>
				<db.connectionURL>${db.connectionURL}</db.connectionURL>
				<db.username>${db.username}</db.username>
				<db.password>${db.password}</db.password>
				<!-- JMS Properties -->
				<jms.factory.initial>${jms.factory.initial}</jms.factory.initial>
				<jms.provider.url>${jms.provider.url}</jms.provider.url>
				<jms.queue>${jms.queue}</jms.queue>
			</properties>
		</profile>
		<profile>
			<id>test</id>
			<!-- Test Env. Properties -->
			<properties>
				<profile.name>${profile.name}</profile.name>
				<!-- Database Properties -->
				<db.driverClass>${db.driverClass}</db.driverClass>
				<db.connectionURL>${db.connectionURL}</db.connectionURL>
				<db.username>${db.username}</db.username>
				<db.password>${db.password}</db.password>
				<!-- JMS Properties -->
				<jms.factory.initial>${jms.factory.initial}</jms.factory.initial>
				<jms.provider.url>${jms.provider.url}</jms.provider.url>
				<jms.queue>${jms.queue}</jms.queue>
			</properties>
		</profile>
		<profile>
			<id>prod</id>
			<!-- Prod Env. Properties -->
			<properties>
				<profile.name>${profile.name}</profile.name>
				<!-- Database Properties -->
				<db.driverClass>${db.driverClass}</db.driverClass>
				<db.connectionURL>${db.connectionURL}</db.connectionURL>
				<db.username>${db.username}</db.username>
				<db.password>${db.password}</db.password>
				<!-- JMS Properties -->
				<jms.factory.initial>${jms.factory.initial}</jms.factory.initial>
				<jms.provider.url>${jms.provider.url}</jms.provider.url>
				<jms.queue>${jms.queue}</jms.queue>
			</properties>
		</profile>
	</profiles>

	<build>

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

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<!-- specify UTF-8, ISO-8859-1 or any other file encoding -->
					<encoding>${project.build.sourceEncoding}</encoding>
				</configuration>
			</plugin>
		</plugins>

		<resources>
			<!-- Placeholders that are found from the files located in the configured 
				resource directories are replaced with the property values found from the 
				profile specific configuration file. -->
			<resource>
				<directory>${resource.directory}</directory>
				<filtering>true</filtering>
			</resource>
		</resources>

	</build>

</project>

Also, we have the below settings.xml XML file, contains the previous POM profiles where each one contains the properties values.

Although, there are more than one elements which configure the core behavior of Maven like (servers, mirrors, proxies, profiles, activeProfiles, etc.), We will focus on profiles and activeProfiles which serve our topic.

settings.xml:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">

	<!-- Active Profile Section -->
	<activeProfiles>
		<activeProfile>dev</activeProfile>
	</activeProfiles>

	<profiles>
		<profile>
			<id>dev</id>
			<!-- Dev Env. Properties -->
			<properties>
				<profile.name>dev</profile.name>
				<!-- Database Properties -->
				<db.driverClass>com.mysql.jdbc.Driver</db.driverClass>
				<db.connectionURL>jdbc:mysql://localhost:3306/emp</db.connectionURL>
				<db.username>dev_usr</db.username>
				<db.password>dev_pss</db.password>
				<!-- JMS Properties -->
				<jms.factory.initial>
                  org.apache.activemq.jndi.ActiveMQInitialContextFactory
                </jms.factory.initial>
				<jms.provider.url>tcp://localhost:61616</jms.provider.url>
				<jms.queue>dev.queue</jms.queue>
			</properties>
		</profile>
		<profile>
			<id>test</id>
			<!-- Test Env. Properties -->
			<properties>
				<profile.name>test</profile.name>
				<!-- Database Properties -->
				<db.driverClass>com.mysql.jdbc.Driver</db.driverClass>
				<db.connectionURL>jdbc:mysql://192.168.1.2:3306/emp</db.connectionURL>
				<db.username>test_usr</db.username>
				<db.password>test_pss</db.password>
				<!-- JMS Properties -->
				<jms.factory.initial>
                  org.apache.activemq.jndi.ActiveMQInitialContextFactory
                </jms.factory.initial>
				<jms.provider.url>tcp://192.168.1.2:61616</jms.provider.url>
				<jms.queue>test.queue</jms.queue>
			</properties>
		</profile>
		<profile>
			<id>prod</id>
			<!-- Prod Env. Properties -->
			<properties>
				<profile.name>prod</profile.name>
				<!-- Database Properties -->
				<db.driverClass>com.mysql.jdbc.Driver</db.driverClass>
				<db.connectionURL>jdbc:mysql://192.168.1.1:3306/emp</db.connectionURL>
				<db.username>prod_usr</db.username>
				<db.password>prod_pss</db.password>
				<!-- JMS Properties -->
				<jms.factory.initial>
                  org.apache.activemq.jndi.ActiveMQInitialContextFactory
                </jms.factory.initial>
				<jms.provider.url>tcp://192.168.1.1:61616</jms.provider.url>
				<jms.queue>prod.queue</jms.queue>
			</properties>
		</profile>
	</profiles>
</settings>

4. Properties File

We have the following properties file application.properties where we need to load specific-environment values for each property in that file.

application.properties:

# Database Properties
db.driverClass=${db.driverClass}
db.connectionURL=${db.connectionURL}
db.username=${db.username}
db.password=${db.password}

# JMS Properties
jms.factory.initial=${jms.factory.initial}
jms.provider.url=${jms.provider.url}
jms.queue=${jms.queue}

5. ApplicationProperties Spring Component

We create ApplicationProperties.java as a Spring component class annotated by @Component which will be a singleton bean, we can autowire it to get properties using getProperty(String propName).

ApplicationProperties.java:

package com.jcg.prop;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.core.env.Environment;

/**
 * @author ashraf_sarhan
 * 
 */
@Configuration
@PropertySources({ @PropertySource(value = "properties/application.properties", ignoreResourceNotFound = true) })
public class ApplicationProperties {

	@Autowired
	private Environment env;

	public String getProperty(String propName) {
		return env.getProperty(propName);
	}

}
  1. Register a Properties File via Java Annotations:
    Spring 3.1 also introduces the new @PropertySource annotation, as a convenient mechanism of adding property sources to the environment. This annotation is to be used in conjunction with Java based configuration and the @Configuration annotation:

    @Configuration
    @PropertySources({ @PropertySource(value = "properties/application.properties", ignoreResourceNotFound = true) })
    
  2. Using / Injecting Properties:
    Unlike Our previous example where we are using @Value for injecting a property, we will obtain the value of a property with the new Environment API:

    @Autowired
    private Environment env;
    
    public String getProperty(String propName) {
    	return env.getProperty(propName);
    }
    
  3. Properties Wrappers:
    As you can notice that we have different properties type such Database, JMS, etc. So, for more organized properties management, we wrapped each type in a wrapper Spring components 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 can autowire them and getting any property value through its getter 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.

    As you notice that we have an init() annotated by @PostConstruct, this method will be executed after dependency injection is done to initialize properties values after beans instantiation.

    DatabaseProperties.java:

    package com.jcg.prop;
    
    import javax.annotation.PostConstruct;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * @author ashraf_sarhan
     * 
     */
    @Component
    public class DatabaseProperties {
    
    	@Autowired
    	private ApplicationProperties applicationProperties;
    
    	private String driverClass;
    
    	private String connectionURL;
    
    	private String username;
    
    	private String password;
    
    	@PostConstruct
    	private void init() {
    		this.driverClass = applicationProperties
    				.getProperty(PropertiesConstants.DB_DRIVERCLASS);
    		this.connectionURL = applicationProperties
    				.getProperty(PropertiesConstants.DB_CONNECTION_URL);
    		this.username = applicationProperties
    				.getProperty(PropertiesConstants.DB_USERNAME);
    		this.password = applicationProperties
    				.getProperty(PropertiesConstants.DB_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 javax.annotation.PostConstruct;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * @author ashraf_sarhan
     * 
     */
    @Component
    public class JmsProperties {
    
    	@Autowired
    	private ApplicationProperties applicationProperties;
    
    	private String factoryInitial;
    
    	private String providerUrl;
    
    	private String queue;
    
    	@PostConstruct
    	private void init() {
    		this.factoryInitial = applicationProperties
    				.getProperty(PropertiesConstants.JMS_FACTORY_INITIAL);
    		this.providerUrl = applicationProperties
    				.getProperty(PropertiesConstants.JMS_PROVIDER_URL);
    		this.queue = applicationProperties
    				.getProperty(PropertiesConstants.JMS_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 + "]";
    	}
    
    }
    

Also, we have a supplementary class PropertiesConstants.java which contains properties keys constants which are used through the code.

PropertiesConstants.java:

package com.jcg.prop;

/**
 * @author ashraf_sarhan
 * 
 */
public class PropertiesConstants {

	// Database Properties Constants
	public static final String DB_DRIVERCLASS = "db.driverClass";
	public static final String DB_CONNECTION_URL = "db.connectionURL";
	public static final String DB_USERNAME = "db.username";
	public static final String DB_PASSWORD = "db.password";

	// JMS Properties Constants
	public static final String JMS_FACTORY_INITIAL = "jms.factory.initial";
	public static final String JMS_PROVIDER_URL = "jms.provider.url";
	public static final String JMS_QUEUE = "jms.queue";

}

Finally, to allow the autowiring of our ApplicationProperties.java Spring component, we created the following Spring context file.

app-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>

6. ApplicationProperties Unit Test

Now, it’s the time for testing the previous code. Let’s run our JUnit test class ApplicationPropertiesTest.java and see the output.

As you will see, we can running our unit test using two ways:

  1. Running the unit test while building the project with a specific profile by executing the following command line from inside the project directory where we can explicitly activate any profile through binding its id value using the -P CLI Maven option, This option takes an argument that is a comma-delimited list of profile-ids to use. When this option is specified, no profiles other than those specified in the option argument will be activated.
    mvn clean install -Pdev -s settings.xml
    
  2. This time, we will run the unit test while building the project with a specific profile but we will implicitly activate our desired profile via the <activeProfiles> section in Maven settings.xml file, This section takes a list of elements, each containing a profile-id inside.
    1. Firstly, we will add the <activeProfiles> section in our settings.xml file.
      <settings>
        ...
        <activeProfiles>
          <activeProfile>dev</activeProfile>
        </activeProfiles>
        ...
      </settings>
      
    2. Finally, we will execute the following command line from inside the project directory.
      mvn clean install -s settings.xml
      
Tip
As you notice in both ways, We are binding settings.xml using the -s CLI Maven option, This option takes an argument that is a Maven settings absolute file path.

ApplicationPropertiesTest.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.JmsProperties;

/**
 * @author ashraf_sarhan
 * 
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/app-context.xml")
public class ApplicationPropertiesTest extends TestCase {
	
	@Autowired
	private DatabaseProperties databaseProperties;
	
	@Autowired
	private JmsProperties jmsProperties;
	
	@Test
	public void testApplicationProperties() {
		
		// Using application properties through properties wrappers
		System.out.println(databaseProperties.toString());
		System.out.println(jmsProperties.toString());
		
	}
	
}

Output:

cli-output
Figure 2: CLI Output

7. 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: SpringMavenPropertiesExampleCode.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
Ramsen
Ramsen
6 years ago

I get “Failed to load ApplicationContext” when running this code. How would I fix this?

Back to top button