Spring Tutorials

Spring Boot Autoconfiguration – Spring @Conditional Annotation

Pinterest LinkedIn Tumblr

While developing Spring-based applications, you may come across a need to register beans conditionally.
For example, you may want to register a DataSource bean pointing to the DEV database when running
applications locally and point to a different PRODUCTION database when running in production.

To address this issue, Spring 3.1 introduced the concept of profiles. You can register multiple beans of
the same type and associate them with one or more profiles. When you run the application, you can activate
the desired profile(s). That way, only the beans associated with the activated profiles will be registered.

@Configuration
public class AppConfig
{
@Bean
@Profile("DEV")
public DataSource devDataSource() {
...
}
@Bean
@Profile("PROD")
public DataSource prodDataSource() {
...
}
}

With this configuration, you can specify the active profile using the -Dspring.profiles.active=DEV system property. This approach works fine for simple cases, such as when you are enabling or disabling bean registrations based on activated profiles. But if you want to register beans based on some conditional logic, the profiles approach itself is not sufficient.

To provide much more flexibility for registering Spring beans conditionally, Spring 4 introduced the concept of the @Conditional. Using the @Conditional approach, you can register a bean conditionally based on any arbitrary condition.

To understand the usage of annotation @Conditional clearly, we’ll look at the following example:

DatabaseType.java

package com.learncode24h.conditional;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Conditional;


@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DatabaseTypeCondition.class)
public @interface DatabaseType
{
	String value();
}

DatabaseTypeCondition.java

package com.learncode24h.conditional;

import java.util.Map;

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

public class DatabaseTypeCondition implements Condition
{
	@Override
	public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
	{
		Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
		String type = (String) attributes.get("value");
		String enabledDBType = System.getProperty("dbType","MYSQL");
		return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));		
	}	
}

AppConfig.java

package com.learncode24h.conditional;

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

@Configuration
@ComponentScan
public class AppConfig
{
	
	@Bean
	@DatabaseType("MYSQL")
	public UserDAO mySQLUserDAO(){
		return new MySQLUserDAO();
	}
	
	@Bean
	@DatabaseType("MONGO")
	public UserDAO mongoUserDAO(){
		return new MongoUserDAO();
	}
}

UserDAO.java

package com.learncode24h.conditional;

import java.util.List;


public interface UserDAO
{
	List<String> getUsers();
}

MySQLUserDAO.java

package com.learncode24h.conditional;

import java.util.Arrays;
import java.util.List;

public class MySQLUserDAO implements UserDAO
{
	@Override
	public List<String> getUsers()
	{
		System.out.println("Getting users from MySQL");
		return Arrays.asList("Robert", "Jack", "Ken", "Mike");
	}
}

MongoUserDAO.java

package com.learncode24h.conditional;

import java.util.Arrays;
import java.util.List;

public class MongoUserDAO implements UserDAO
{

	@Override
	public List<String> getUsers()
	{
		System.out.println("Getting users from MongoDB");
		return Arrays.asList("Robert", "Jack", "Ken", "Mike", "Jenny");
	}
}

If you want to connect web application to MongoDB you only need to set app.dbType=MONGO and vice versa to MySQL you need to set app.dbType=MYSQL. DatabaseTypeCondition class will match value of annotation @DatabaseType with config value in app.dbType and choose the matching database.

In reality to speedup the development time, we can use Spring Boot’s Build-In @Conditional anotations. Below is the most popular Spring Boot’s Build-In @Conditional anotations

Spring Boot’s Build-In @Conditional anotations

AnnotationDescription
@ConditionalOnBean
Matches when the specified bean classes and/or names are
already registered
@ConditionalOnMissingBeanMatches when the specified bean classes and/or names are not
already registered
@ConditionalOnClass
Matches when the specified classes are on the classpath.
@ConditionalOnMissingClass
Matches when the specified classes are not on the classpath
@ConditionalOnProperty
Matches when the specified properties have a specific value.
@ConditionalOnResource
Matches when the specified resources are on the classpath.
@ConditionalOnWebApplication
Matches when the application context is a web application
context.

@ConditionalOnProperty

@Bean
@ConditionalOnProperty(
        value = "email.error.log",
        havingValue = "true",
        matchIfMissing = false
)
public Email getEmail(){
    return new Email();
}

The @ConditionalOnPropertyannotation allows loading beans conditionally depending on a certain configurational property. In the above example, Email bean will only be loaded if in the config file, theemail.error.logattribute is set totrue.If config is not found Spring will set true for matching by default, so matchIfMissing = false in this case will help avoid matching if config is not found.

@ConditionalOnBean

@Bean
@ConditionalOnBean(Factory.class)
public Car getCar(){
    return new Car();
}

Car bean will be build if Factory bean class is there in the application context. We can also put bean name instead of bean class.

@ConditionalOnResource

@Bean
@ConditionalOnResource(
        resources = "/db.properties"
)
public DatabaseBean getDatabaseBean(){
    return new DatabaseBean ();
}

The Database bean will only be loaded in the application context if we have db.properties in our classpath.

I'm a full stack developer. I have experiences with Java, Android, PHP, Python, C#, Web development...I hope website https://learncode24h.com will help everyone can learn code within 24h and apply it in working easily.

Comments are closed.