Close

Spring - @Bean conditional registration

[Last Updated: Jun 11, 2021]

Spring 4, introduced a new annotation @Conditional, which is meant to be used on @Bean methods or (in case of @ComponentScan) with @Component classes, or the @Configuration classes.


The purpose of this annotation is to enable/disable target beans from being registered based on some condition. These conditions are applied at the time of the container startup.


Definition of @Conditional annotation

package org.springframework.context.annotation;
.....
public @interface Conditional {
  Class<? extends Condition>[] value();

}

Definition of Condition interface

package org.springframework.context.annotation;
 .....
public interface Condition {
  boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

How it works?




@Conditional vs @Profile

The use of @Conditional and Condition is is similar to selecting different profiles using @Profile but is more flexible because the conditions are specified in Java code rather than using some pre defined property flag or some system property.

Spring uses @Conditional on @Profile definition as meta annotation. That means @Conditional concept is on lower level than that of @Profile.

Spring profiles have been around since 3.1 and they were refactored in 4.0 to take advantage of this new way of applying conditions on bean registration. Here's the code source snippets of @Profile and ProfileCondition

.....
@Conditional(ProfileCondition.class)
public @interface Profile {
  ....
}
class ProfileCondition implements Condition {

 @Override
 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  if (context.getEnvironment() != null) {
   MultiValueMap<String, Object> attrs =
                          metadata.getAllAnnotationAttributes(
                                                       Profile.class.getName());
  if (attrs != null) {
   for (Object value : attrs.get("value")) {
    if (context.getEnvironment().acceptsProfiles(
                                              ((String[]) value))) {
     return true;
    }
   }
   return false;
  }
 }
 return true;
 }
}

What is ConditionContext?

ConditionContext is the first parameter of ProfileCondition#matches method. This facade interface was introduced to work with Condition. We can query container related information via this interface. e.g. we can get instance of ConfigurableListableBeanFactory, Environment, BeanDefinitionRegistry etc.



What is AnnotatedTypeMetadata?

AnnotatedTypeMetadata helps retrieving information about specified annotation type used on underlying element i.e. the same element annotated with @Conditional: the method with @Bean or class with @Component or @Configuration.

This meta data helper can get us the attributes of annotations like @Bean, @Component, @Lazy, @Scope etc, used on the same method or class.

Example

Beans

package com.logicbig.example;

public interface MyService {
}
package com.logicbig.example;

public class MyServiceA implements MyService {
}
package com.logicbig.example;

public class MyServiceB implements MyService {
}
package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;

public class ClientBean {
  @Autowired
  private MyService myService;

  public MyService getMyService () {
      return myService;
  }
}

Defining beans with condition

package com.logicbig.example;

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

@Configuration
public class AppConfig {
  @Bean
  @Conditional(LocaleConditionUSA.class)
  public MyService myBeanA () {
      return new MyServiceA();
  }

  @Bean
  @Conditional(LocaleConditionCanada.class)
  public MyService myBeanB () {
      return new MyServiceB();
  }

  @Bean
  public ClientBean clientBean () {
      return new ClientBean();
  }
}

Implementations of Condition

package com.logicbig.example;

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

import java.util.Locale;

public class LocaleConditionCanada implements Condition {

  @Override
  public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) {
      return Locale.getDefault()
                   .equals(Locale.CANADA);
  }
}
package com.logicbig.example;

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

import java.util.Locale;

public class LocaleConditionUSA implements Condition {

  @Override
  public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) {
      return Locale.getDefault()
                   .equals(Locale.US);
  }
}

Main class

package com.logicbig.example;


import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Locale;

public class BeanConditionExample {

    public static void main(String[] args) {
        //In real application, System Locale would be used depending on your location.
        //Here we are setting it manually to test our example*/
        runApp(Locale.US);
        System.out.println("----------");
        runApp(Locale.CANADA);
    }

    public static void runApp(Locale locale) {
        System.out.printf("setting default locale: %s\n", locale);
        Locale.setDefault(locale);
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(
                        AppConfig.class);

        ClientBean bean = context.getBean(ClientBean.class);
        System.out.printf("Injected MyService instance in ClientBean: %s\n", bean.getMyService()
                                                                                 .getClass()
                                                                                 .getSimpleName());
    }
}

Output

setting default locale: en_US
Injected MyService instance in ClientBean: MyServiceA
----------
setting default locale: en_CA
Injected MyService instance in ClientBean: MyServiceB

Example with ComponentScan

package com.logicbig.example;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Locale;

@Configuration
@ComponentScan(basePackageClasses = BeanConditionScanExample.class,
        useDefaultFilters = false,
        includeFilters = {@ComponentScan.Filter(
                type = FilterType.ASSIGNABLE_TYPE,
                value = {BeanConditionScanExample.MyClientBean.class,
                        BeanConditionScanExample.ServiceBeanImpl1.class,
                        BeanConditionScanExample.ServiceBeanImpl2.class})})

public class BeanConditionScanExample {

    public static void main(String[] args) {
        runApp(Locale.US);
        System.out.println("----------");
        runApp(Locale.CANADA);
    }

    public static void runApp(Locale locale) {
        System.out.printf("setting default locale: %s\n", locale);
        Locale.setDefault(locale);
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(
                        BeanConditionScanExample.class);
        MyClientBean bean = context.getBean(MyClientBean.class);
        System.out.printf("Injected ServiceBean instance in MyClientBean: %s\n", bean.getServiceBean()
                                                                                     .getClass()
                                                                                     .getSimpleName());
    }

    @Component
    public static class MyClientBean {
        @Autowired
        private ServiceBean serviceBean;

        public ServiceBean getServiceBean() {
            return serviceBean;
        }
    }

    public interface ServiceBean {
    }

    @Component
    @Conditional(LocaleConditionUSA.class)
    public static class ServiceBeanImpl1 implements ServiceBean {
    }

    @Component
    @Conditional(LocaleConditionCanada.class)
    public static class ServiceBeanImpl2 implements ServiceBean {
    }
}

Output

setting default locale: en_US
Injected ServiceBean instance in MyClientBean: ServiceBeanImpl1
----------
setting default locale: en_CA
Injected ServiceBean instance in MyClientBean: ServiceBeanImpl2

Example Project

Example Project

Dependencies and Technologies Used:

  • spring-context 5.3.8: Spring Context. Version Compatibility: 4.0.0.RELEASE - 5.3.8 Version List
    ×

    Version compatibilities of spring-context with this example:

    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8

    Versions in green have been tested.

  • JDK 8
  • Maven 3.8.1

@Bean conditional registration Select All Download
  • bean-conditional-registration
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • LocaleConditionUSA.java

    See Also