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 Settings. Our 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
- Spring 4.1.5
- Spring Test 4.1.5
- JUnit 4.12
- Apache Maven 3.0.5
- JDK 1.8
- Eclipse 4.4 (Luna)
2. Project Structure
We create a simple Spring Maven project with the following 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); } }
- 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) })
- 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); }
- 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 theDatabaseProperties.java
and all JMS properties were wrapped in theJmsProperties.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:
- 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
- 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.- Firstly, we will add the
<activeProfiles>
section in oursettings.xml
file.<settings> ... <activeProfiles> <activeProfile>dev</activeProfile> </activeProfiles> ... </settings>
- Finally, we will execute the following command line from inside the project directory.
mvn clean install -s settings.xml
- Firstly, we will add the
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:
7. Download the Source Code of this example
This was an example on how to load environment configurations and properties with Spring.
You can download the full source code of this example here: SpringMavenPropertiesExampleCode.zip
I get “Failed to load ApplicationContext” when running this code. How would I fix this?