Sunday, September 10, 2017

Load Environment Specific Properties Files Using Spring

One of the more common aspects of enterprise application development involves environment specific properties files. Some examples include Database configurations or external JMS resources etc. The common way to address this problem is to use multiple properties file an build the Application EAR/WAR/JAR at compile time. Although this works, this solution also means that we maintain different build scripts for different environments, or having some file renames etc. while building the application. Spring profiles address this problem in a more efficient way. Like any other property, using Spring profiles, we can inject the environment profile into Spring and Spring will handle the loading of the appropriate configuration files. In this post, I show how to load environment specific properties files using Spring. This example shows a simple application that has three environments dev,qa, and prod. Each environment has it's own properties files and we expect to print the properties based on the environment property passed from command line. Here are the important steps
  • Define profiles in Spring context file
    <beans profile="dev">
     <context:property-placeholder
      location="classpath:config-default.properties, classpath:config-dev.properties"
      ignore-unresolvable="true" />
    </beans>
  • Inject the environment variables using ${} into the required classes.
    @Value("${app.name}")
    private String appName;
  • Pass the profile information from command line, or update environment in code.
    gradle run -Dspring.profiles.active=prod
    ConfigurableEnvironment env = (ConfigurableEnvironment) context.getEnvironment();
      env.setActiveProfiles("qa");
      ((AbstractApplicationContext) context).refresh();
Here is the full code

Directory Structure

SpringEnvProperties
└───src
    ├───main
    │   ├───java
    │   │   └───com
    │   │       └───aoj
    │   │           │   SpringEnvProperties.java
    │   │           │
    │   │           └───service
    │   │                   DependsOnEnv.java
    │   │
    │   └───resources
    │           applicationContext.xml
    │           config-default.properties
    │           config-dev.properties
    │           config-prod.properties
    │           config-qa.properties
    │
    └───test
        └───java

applicationContext.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://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

 <context:component-scan base-package="com.aoj.service" />

 <beans profile="dev">
  <context:property-placeholder
   location="classpath:config-default.properties, classpath:config-dev.properties"
   ignore-unresolvable="true" />
 </beans>

 <beans profile="qa">
  <context:property-placeholder
   location="classpath:config-default.properties, classpath:config-qa.properties"
   ignore-unresolvable="true" />
 </beans>

 <beans profile="prod">
  <context:property-placeholder
   location="classpath:config-default.properties, classpath:config-prod.properties"
   ignore-unresolvable="true" />
 </beans>

</beans>

config-default.properties

app.name=SpringEnvProperties

config-dev.properties

env.name=dev
env.prop1=propDev

config-qa.properties

env.name=qa
env.prop1=propQA

config-prod.properties

env.name=prod
env.prop1=propProduction

SpringEnvProperties.java

package com.aoj;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

import com.aoj.service.DependsOnEnv;

public class SpringEnvProperties {
 public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/applicationContext.xml");
  System.out.println("Properties : " + System.getProperty("spring.profiles.active"));

  DependsOnEnv test = context.getBean(DependsOnEnv.class);
  test.printEnv();

  // Update environment from code.
  ConfigurableEnvironment env = (ConfigurableEnvironment) context.getEnvironment();
  env.setActiveProfiles("qa");
  ((AbstractApplicationContext) context).refresh();

  test = context.getBean(DependsOnEnv.class);
  test.printEnv();

 }
}

DependsOnEnv.java

package com.aoj.service;

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

@Component
public class DependsOnEnv {

 @Value("${app.name}")
 private String appName;

 @Value("${env.name}")
 private String envName;

 @Value("${env.prop1}")
 private String envProp1;

 public void printEnv() {
  System.out.println("App Name : " + appName);
  System.out.println("Current Env : " + envName);
  System.out.println("Current Env Prop : " + envProp1);
 }

 public String getAppName() {
  return appName;
 }

 public void setAppName(String appName) {
  this.appName = appName;
 }

 public String getEnvName() {
  return envName;
 }

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

 public String getEnvProp1() {
  return envProp1;
 }

 public void setEnvProp1(String envProp1) {
  this.envProp1 = envProp1;
 }

}

build.gradle

apply plugin:'application'
mainClassName = "com.aoj.SpringEnvProperties"
applicationName = 'SpringEnvProperties'
repositories {
    mavenCentral()
}

dependencies {
  compile group: 'org.springframework', name: 'spring-core', version: '4.3.9.RELEASE'
 compile group: 'org.springframework', name: 'spring-context', version: '4.3.9.RELEASE'
 compile group: 'org.springframework', name: 'spring-beans', version: '4.3.9.RELEASE'
 compile group: 'org.springframework', name: 'spring-tx', version: '4.3.9.RELEASE'
 compile group: 'org.springframework', name: 'spring-orm', version: '4.3.9.RELEASE'

    // Use JUnit test framework
    compile 'junit:junit:4.12'
}

// Required to pass gradle command-line system properties to the Java process
run {    
    systemProperty "spring.profiles.active", System.getProperty("spring.profiles.active")

}

Caution for Gradle users

While using gradle, if you do not pass the System properties using "systemProperty" as shown in the build.gradle file above, you will see that the properties will not be passed to the java program, and you might see something like below
INFO: Skipped XML bean definition file due to specified profiles [prod] not matching: class path resource [applicationContext.xml]
Properties : null
App Name : ${app.name}
Current Env : ${env.name}
Current Env Prop : ${env.prop1}
You can see that instead of the environment properties, it will just print the ${}.

No comments:

Post a Comment

Popular Posts