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
Annotation | Description |
@ConditionalOnBean | Matches when the specified bean classes and/or names are already registered |
@ConditionalOnMissingBean | Matches 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 @ConditionalOnProperty
annotation 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.log
attribute 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.
Comments are closed.