Spring 4 – Conditional Bean Configuration

by | Dec 13, 2013

As part of my series on the new features in Spring Framework, version 4, I’d like to present how to use the new Condition interface and @Conditional annotation.  Spring 4 was released in GA in 2013.  Yet you may be interested. in this?

There are times when you would like the creation and dependency injection of a bean to depend on circumstances.  Those circumstance could be what operating system your application is running in, or what application server it finds itself on.  Bean configuration may also depend on what version of Java is available, the value of a system property/environmental variable, or whether your application is running in dev, test, stage, or production.  For these and a host of other reasons, you may want to conditionalize the instantiation and wiring of your Spring beans.

Spring Expression Language

Spring 3 brought us Spring Expression Language (SpEL) and it offered us some capability to conditionalize the wiring of beans.  In my post on SpEL a few months ago, I showed several examples of how to use SpEL to more dynamically determine when/how to create and wire beans.  On such example demonstrate how to use SpEL to create the appropriate data source based on the operating system architecture.

public class MyDao {
  @Value("#{systemProperties['os.arch'].equals('x86') ? winDataSource : unixDataSource}")
  private DataSource datasource;
  ...
}
Spring 3.1 and Profiles

Spring 3.1 provided for bean configuration/creation based on a profile. That is, the ability to use configuration classes and create beans based on certain named circumstance.

To demonstrate this feature, imagine your application required a different bean configuration based on the environment it found itself in. For example sake, your application required a different EmailService bean depending on whether the application was running in Linux or Windows. You could write the conditional directly into the @Bean method.

@Configuration
public class MyConfiguration {

  @Bean
  public EmailService emailerService(){
    if (System.getProperty("os.name").contains("Windows")){
      return new WindowsEmailService();
    }
    return new LinuxEmailService();
  }
}

As of Spring 3.1, an alternative to this hard-coded approach (what happens if other operating systems need to be supported?) is to use a profile.  A profile, defined with @Profile, allows you to name a logical grouping of beans.  In this example, you would need two profiles:  one for Windows and one for Linux.

@Configuration
@Profile("Linux")
public class LinuxConfiguration {

  @Bean
  public EmailService emailerService() {
    return new LinuxEmailService();
  }
}
@Configuration 
@Profile("Windows") 
public class WindowsConfiguration {

  @Bean
  public EmailService emailerService() {
    return new WindowsEmailService();
  }
}

With the profiles in place, use setActiveProfiles( ) on the AnnotationConfigApplicationContext to select the appropriate emailer.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("Linux");
context.scan("com.intertech");
context.refresh();
Spring 4.0 Conditional Bean Configuration

Spring 4 adds a new @Conditional annotation that allows for a similar conditionalized configuration, but one that does not require a profile.  Using the operating system example again, you would need to create two classes that implement the Spring Condition interface.  This interface requires the implementation of a matches( ) method.  The matches( ) method checks for a condition and returns a boolean indicating whether that condition is met.

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class LinuxCondition implements Condition{

  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    return context.getEnvironment().getProperty("os.name").contains("Linux");  }
}
import org.springframework.context.annotation.Condition; 
import org.springframework.context.annotation.ConditionContext; 
import org.springframework.core.type.AnnotatedTypeMetadata; 

public class WindowsCondition implements Condition{

  @Override 
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    return context.getEnvironment().getProperty("os.name").contains("Windows");
  }
}

The ConditionContext parameter to matches( ) provides access to the environment, container, class loader, etc. that the condition may use to make its boolean-producing determination.  The AnnotatedTypeMetadata parameter to matches( ) provides access to the method on which the @Conditional using the Condition is applied to (see below).

With the conditions in place, now annotate your Configuration bean methods with @Conditional and the condition checking class as a parameter.  Note how both methods in the Configuration return an EmailService implementation named “emailerService”.  However, only one of these methods will get called by the container and create the emailerService based on the conditions provided through @Conditional.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfiguration {

  @Bean(name="emailerService")
  @Conditional(WindowsCondition.class)
  public EmailService windowsEmailerService(){
      return new WindowsEmailService();
  }

  @Bean(name="emailerService")
  @Conditional(LinuxCondition.class)
  public EmailService linuxEmailerService(){
    return new LinuxEmailService();
  }
}

The example here uses the conditions for the creation of just one bean.  However, the conditions serve as a more reusable mechanism, which could be used to conditionalize all sorts of beans.