Sunday, May 8, 2022

Spring Profiles With Examples

Spring Profiles provide a way to segregate parts of your application configuration, you can mark your beans using @Profile to map them with specific profile and those beans are loaded only when that profile is active. Thus using Spring profiles you can conditionally register bean when one or more specified profiles are active.

For example while developing an application you may use different DB configurations, one set of DB configuration in QA environment and in production altogether different DB configuration. Using Spring profile you can create separate beans for QA and production and load mapped beans for QA when QA profile is active and load mapped beans for production when production profile is active.

Some other scenarios where Spring profiles can be used to switch environments are encryption techniques, caching, interaction with other systems may vary among different deployment environments.

In this post we'll see how you can create different profiles for different environment in Spring framework using @profile annotation and configure which profile should be activated as per environment.


Spring profiles example

Consider the use case where application requires different DataSource configuration for different environments.

In 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.

What you need here is register certain bean definitions in certain contexts, while not in others and that's where Spring profiles come into picture.

Using Spring profiles

Spring profile allows 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.

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 @Profile annotation in Spring 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 @Profile at the method level is used.

Spring @Profile at method level

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.

Setting default profile

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 Spring

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();
 }
}

Using Spring XML Configuration to define 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 Spring 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 Spring Profiles With Examples. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Using Conditional Annotation in Spring Framework
  2. Spring Component Scan Example
  3. How to Read Properties File in Spring Framework
  4. Data access in Spring framework
  5. Spring NamedParameterJdbcTemplate Insert, Update And Delete Example

You may also like-

  1. Dependency Injection in Spring Framework
  2. Bean Definition Inheritance in Spring
  3. Creating a Maven project in Eclipse
  4. Difference Between Comparable and Comparator in Java
  5. How to Sort HashSet in Java
  6. forEach statement in Java 8
  7. Why wait(), notify() And notifyAll() Must be Called Inside a Synchronized Method or Block
  8. static Block in Java

1 comment:

  1. Thanks this is one of in-depth guide of Spring profile.
    I have question , Can we have one or more profile with same name ? if so what will be the execution order ?

    ReplyDelete