Spring Basics: @Conditional Bean Wiring

Table of Contents

A Use Case for Conditional Bean Wiring

Suppose we want to enable caching for our web application. We are interested to find out whether this helps our application or whether the overhead will so big that it actually slows our application down. We decide we want to put the new functionality behind a feature toggle. We add a new property to our application called app.caching.enabled=false. When we are ready to enable the caching, we change the property’s value to true and we are in business. When the results disappoint, we can easily revert the property’s value to false.

@Conditional Introduction

The @Conditional annotation can be used on any type or method that declares a bean:

@Conditional(SomeCondition.class)
@Bean
public SomeBean someBean() {
    return new SomeBean();
}

@Conditional takes a mandatory class that implements the functional interface Condition. Condition defines a single method matches that returns a boolean. The method decides whether the bean should be loaded into the Spring context or whether it should be ignored:

public SomeCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        /* make decision based on context and metadata */
    }
}

A @Conditional @Configuration

When @Conditional is applied to a class which is also annotated with @Configuration, then all of the @Bean methods, @Import, @ComponentScan and other annotations will be subject to the condition. This means that when the condition evaluates to false, all of the configuration defined in that class will be ignored.

Since creating a cache carries costs with it (even an unused cache reserves space on the heap for its initial size), this is exactly what we want in our situation.

@Configuration
@Conditional(CacheCondition.class)
public class CacheConfig {

    /* ... define cache manager and caches here ... */

    public static CacheCondition implements ConfigurationCondition {
        public ConfigurationPhase getConfigurationPhase() {
            return ConfigurationPhase.PARSE_CONFIGURATION;
        }

        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return context.getEnvironment().getRequiredProperty("app.caching.enabled", Boolean.class);
        }
    }
}

Since we are dealing with a conditional configuration instead of a regular bean, the condition class implements ConfigurationCondition instead of Condition. The ConfigurationCondition makes us implement another method that specifies at which point the condition should be evaluated. Normally, this ConfigurationPhase is set to REGISTER_BEAN, which means it checks the condition when adding a regular bean. In this case, we want it set to PARSE_CONFIGURATION, which makes Spring evaluate the condition at the moment the configuration class is parsed. If the condition does not match at that moment, the @Configuration class will not be added to the context.

Dependencies

The only dependency is on spring-context:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.1.RELEASE</version>
</dependency>

Sample Project

A sample project can be found on GitLab.

Leave a Reply

Your email address will not be published. Required fields are marked *