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
- Spring 4.1.3
- Spring Test 4.1.3
- JUnit 4.11
- Apache Log4j 1.2.17
- 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. 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:
- Its own
PropertySourcesPlaceholderConfigurer
configured with its environment-specific properties file. - Its own
ComponentScan
configured to define its environment-specific beans packages to scan.
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
.
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:
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: SpringPropertiesExample Code.zip
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 »