Monday, 5 December 2016

Using Spring profiles to switch environment

While developing an application its very common to switch environments. As example while developing an application we may use different DB configuration, in QA environment another set of DB configuration and in production altogether different DB configuration. Few other examples are encryption techniques, caching, interaction with other systems may vary among different deployment environments.

Bean definition profiles

In order to have different beans for different environment we do need to register different beans in different environments.

Let’s consider the first use case in a practical application that requires a DataSource. In a dev environment, the configuration may look like this:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
      .setType(EmbeddedDatabaseType.HSQL)
      .addScript("my-schema.sql")
      .addScript("my-test-data.sql")
      .build();

In QA environment lets say you are using commons DBCP connection pool -

@Bean(destroyMethod="close")
public DataSource dataSource() {
  BasicDataSource dataSource = new BasicDataSource();
  dataSource.setUrl("jdbc:mysql://localhost:3306/test");
  dataSource.setDriverClassName("com.mysql.jdbc.Driver");
   dataSource.setUsername("user");
  dataSource.setPassword("password");
  dataSource.setInitialSize(10);

  return dataSource;
}

In production environment assuming that the datasource for the application will be registered with the production application server’s JNDI directory. Our dataSource bean now looks like this:

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

So you can see that ultimately you are getting a datasource but all the three environments have their own ways for getting the different versions of data sources.

One way to do that is having a combination of system environment variables and XML <import/> statements containing ${placeholder} tokens that resolve to the correct configuration file path depending on the value of an environment variable.

What you need here is register certain bean definitions in certain contexts, while not in others. Spring provides bean profiles as a better option to do that.

Spring bean profiles

Spring bean profiles allow you to indicate that a component is eligible for registration, it can be done using -

  1. profile annotation with Java configuration.
  2. profile attribute of the <beans> element with XML configuration.

Here note that Spring makes the decision which environment is needed at runtime.

So, to summarize it using profiles is a two step process -

  1. You have to get varying bean definitions into one or more profiles.
  2. Make sure that the proper profile is active when your application is deployed in each environment.

So using @Profle annotation you can write DB configuration for dev environment as -

@Configuration
@Profile("dev")
public class StandaloneDataConfig {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("classpath:com/bank/config/sql/schema.sql")
        .addScript("classpath:com/bank/config/sql/test-data.sql")
        .build();
    }
}
And for production as -
@Configuration
@Profile("production")
public class JndiDataConfig {
    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

Here you can note that @Profile annotation is applied at the class level. That was the only way to do it in Spring 3.1. Starting with Spring 3.2, however, you can use @Profile at the method level, alongside the @Bean annotation. In that case you need only one particular bean of a configuration class. In the preceding example I’ll use @Profile at the method level.

Spring profiles example

In this example there is DBConfiguration class which has different profiles for dev, qa and production.

As this example uses Java configuration so please refer Spring example program using JavaConfig and Annotations to know more about Java configuration.

DBConfiguration class

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
public class DBConfiguration {
    //private static final Logger log = LoggerFactory.getLogger(DBConfiguration.class);
    
    @Bean(destroyMethod="shutdown")
    @Profile("dev")
    public DataSource embeddedDataSource() {
        System.out.println("profile for dev");
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:test-data.sql")
            .build();
    }
    
    @Bean
    @Profile("prod")
    public DataSource jndiDataSource() {
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
    
    @Bean
    @Profile("qa")
    public DataSource qaDataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUsername("user");
        dataSource.setPassword("password");
        dataSource.setInitialSize(10);

        return dataSource;

    }
}

Setting active profile

You can set active profile programmatically against the Environment API which is available via an ApplicationContext:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppProfile {
 public static void main( String[] args ){
  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  // Setting active profile
  context.getEnvironment().setActiveProfiles("dev");
  // Registering configuration class
  context.register(DBConfiguration.class);
  context.refresh();
  context.close();
    }
}

Running this class will give output as “profile for dev” as dev profile is active.

Different types of profiles

Apart from active profile there is also an option to declare a default profile. The default profile represents the profile that is enabled by default.

As example

@Configuration
@Profile("default")
public class DefaultDataConfig {
    @Bean
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
 dataSource.setUrl("jdbc:mysql://localhost:3306/test");
 dataSource.setDriverClassName("com.mysql.jdbc.Driver");
 dataSource.setUsername("user");
 dataSource.setPassword("password");
 dataSource.setInitialSize(10);

 return dataSource;
    }
}

If no profile is active, the dataSource above will be created; this can be seen as a way to provide a default definition for one or more beans. If any profile is enabled, the default profile will not apply.

Different ways of activating profiles

In the above example you have already seen how to activate profile programmatically against the Environment API. But there are several other ways profile can be activated.

  • As initialization parameters on DispatcherServlet
  • As context parameters of a web application
  • As environment variables
  • As JVM system properties
  • Using the @ActiveProfiles annotation on an integration test class

As initialization parameters on DispatcherServlet

<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>
    org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
   <param-name>spring.profiles.active</param-name>
   <param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

As context parameters of a web application

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

As system property

-Dspring.profiles.active = "qa"

OR in the AppProfile class used in above example -

public class AppProfile {
 public static void main( String[] args ){
  System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "qa");
  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DBConfiguration.class);
  // Setting active profile
  //context.getEnvironment().setActiveProfiles("qa");
  // Registering configuration class
  //context.register(DBConfiguration.class);
  //context.refresh();
  context.close();
    }
}

XML Bean definition profiles

Apart from Java Configuration you can also use XML configuration to define various profiles. This can be done by setting the profile attribute of the <beans> element.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- other bean definitions -->
   <beans profile="dev">
       <jdbc:embedded-database id="dataSource">
       <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
       <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
       </jdbc:embedded-database>
   </beans>

   <beans profile="prod">
       <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
   </beans>

   <beans profile="qa">
      <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value = "${db.driverClassName}" />
        <property name="url" value = "${db.url}" />
        <property name="username" value = "${db.username}" />
        <property name="password" value = "${db.password}" />
        <property name="initialSize" value = "${pool.initialSize}" />
    </bean>
  </beans>
</beans>

Here note that there are three beans and all are of type javax.sql.DataSource and with an ID of dataSource. But at runtime, Spring container will create only one bean, depending on which profile is active.

Few Points about Profiles

  • You can enable more than one profile.
    context.getEnvironment().setActiveProfiles("profile1", "profile2");
    
  • You can also use NOT operator (!) with the profile.

    As example, if there are two methods -

    @Bean(destroyMethod="shutdown")
    @Profile("!dev")
    public DataSource embeddedDataSource() {
     ....
     ....
    }
    
    @Bean
    @Profile("qa")
    public DataSource qaDataSource() {
     ....
     ....
    }
    

    In that case registration for dev will occur if profile 'qa' is active or if profile 'dev' is not active. Which, in a way means if qa profile is active apart from qa, dev profile will also be registered, making “dev” profile as active will result in no registration at all.

  • As we have already seen @profile can be used at method level from Spring 3.2. In Spring 3.1 only at class level.
  • You can also have default profile. The default profile represents the profile that is enabled by default.

That's all for this topic Using Spring profiles to switch environment. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Using Conditional annotation in Spring framework
  2. Data access in Spring framework
  3. What is Dependency Injection in Spring
  4. Spring example program using automatic configuration
  5. Bean definition inheritance in Spring

You may also like -

>>>Go to Spring tutorial page

1 comment:

  1. Superb. I really enjoyed very much with this article here. Really it is an amazing article I had ever read. I hope it will help a lot for all. Thank you so much for this amazing posts and please keep update like this excellent article.thank you for sharing such a great blog with us. expecting for your.
    seo company in india

    ReplyDelete