In this post under Spring Core, I will show with example the purpose of “@Conditional” annotation.
Sometimes we want certain beans to be initialized based on a condition. If those conditions are not met we don’t want to initialize those beans.
Say for example, we have two beans “bean1” and “bean2”. We want only “bean1” to be initialized and not “bean2” when we are running application in production environment and only “bean2” to be initialized
and not “bean1” when we are running application in testing environment.
At that time we can use “@Conditional” annotation.
The example I gave can be achieved using “@Profile” annotation covered in one of my previous post.
You will be surprised to know that the functionality of “@Profile” was implementated by using “@Conditional” annotation internally.
Now lets see how to use “@Conditional” annotation with an example.
For our example, we have an interface “IMessageService” and its two implementation “ProdMessageService” and “TestMessageService”.
Which implementation of “IMessageService” to use is decided at runtime based on the value of environment property “profile.active”.
Below is the interface and class structure of “IMessageService” and its implementations “ProdMessageService” and “TestMessageService”
IMessageService
package core.package41;
public interface IMessageService {
public default void display() {
System.out.println(this.getClass().getName());
}
}
ProdMessageService
package core.package41;
public class ProdMessageService implements IMessageService {
}
TestMessageService
package core.package41;
public class TestMessageService implements IMessageService {
}
We also need to have a class that implements “org.springframework.context.annotation.Condition” as shown below
EnvironmentCondition
package core.package41;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.StandardMethodMetadata;
public class EnvironmentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String environment = context.getEnvironment().getProperty("profile.active");
StandardMethodMetadata standardMethodMetadata = (StandardMethodMetadata) metadata;
if((environment != null) && !environment.isEmpty()) {
return standardMethodMetadata.getMethodName().toLowerCase().startsWith(environment);
} else {
throw new IllegalArgumentException("environment property 'profile.active' must be mentioned");
}
}
}
The above class will have the conditional logic implemented. In the above class, we have to provide logic for “matches” method that we got by implementing the “Condition” interface.
This “matches” method take two arugments
1) the Spring context through we can access the environment, system properties etc.
2) the metadata of the class/bean on which this annotation is applied.
In the matches method we are checking whether the method name starts with “profile.active” value. If yes it returns “true” else “false”.
For example if the value of “profile.active” is “prod” and the method name is “prodMessageService” it returns true or else false.
If the return value is “true”, the bean is initialized or else skipped.
Below is the main class using all the previously created classes and interface
Main class
package core.package41;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan(basePackages = "core.package41")
public class Example41 {
@Bean
@Conditional(EnvironmentCondition.class)
public IMessageService prodMessageService() {
return new ProdMessageService();
}
@Bean
@Conditional(EnvironmentCondition.class)
public IMessageService testMessageService() {
return new TestMessageService();
}
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Example41.class);
IMessageService messageService = applicationContext.getBean(IMessageService.class);
messageService.display();
}
}
In the above class, I have defined two beans “prodMessageService” and “testMessageService”.
To both the beans I have added “@Conditional” annotation and its value is a name of the class that implements the conditional logic. In this case it “EnvironmentCondition” class.
In the main static method, at line 23, I get an instance of “IMessageService” class and call “display” at line 24.
So at runtime based on the value of environment property “profile.active” one of the beans “prodMessageService” or “testMessageService” is created and its “display” method is called.
In this way we can use “@Conditional” annotation.
In this post I have shown “@Conditional” applied at method level.
We can also apply this annotation at class level.