Spring 4 is introducing a new feature called Conditional - an annotation targeted towards Spring components which generate beans and vetos the generation of these beans, in essence it provides a way to conditionally generate beans.
Consider a simple example:
I have a service called "CustomerService", with two implementations of this service, say "CustomerService1" and "CustomerService2". Based on the presence of a System property, say "servicedefault", I want to create the default "CustomerService1" implementation and if it is absent I want to create an instance of "CustomerService2".
Using Java configuration based Spring bean definition I would do it this way:
An alternate approach is to use Spring Bean Profiles introduced with Spring 3.1:
However, Profiles in this specific instance is a bit unwieldy as it will be difficult to set a profile for managing the implementation strategy of one bean, it is much more appropriate for cases where the behavior for a set of beans needs to be controlled.
Spring 4 introduces Conditional annotation where this behavior can be achieved in a little more reusable way.
Conditional depends on a set of Condition classes to specify the predicate, this way:
I need two predicates, one to specify the positive condition and one to specify the negative condition, these can now be applied on the bean definitions:
However, note that there is a hardcoded system property name "servicedefault" in the code of the Condition, this can be cleaned up further by using meta annotations. A new meta annotation can be defined this way:
This meta annotation ConditionalOnSystemProperty takes in two user specified attributes - "value" for the system property name and "exists" to check whether the property exists or to check that the property does not exist. The meta annotation is tagged with @Conditional annotation which points to the Condition class to trigger for beans annotated with this new meta annotation, the Condition class is the following:
The logic here is to get hold of the attributes defined on the @Bean instances using the meta-annotation, and to trigger the check for the presence or the absence of the system property based on the additional "exists" attribute. This reusable meta-annotation can now be defined on the @Bean instances to conditionally create the beans, this way:
Wrap Up
The example here is trivial and probably not very realistic and is used purely to demonstrate the Conditional feature. A far better example in Spring 4 is the way Conditional is used for modifying the behavior of Spring 3.1 based Profiles itself that I had mentioned previously, Profiles is internally now based on meta-annotation based Conditional:
Consider a simple example:
I have a service called "CustomerService", with two implementations of this service, say "CustomerService1" and "CustomerService2". Based on the presence of a System property, say "servicedefault", I want to create the default "CustomerService1" implementation and if it is absent I want to create an instance of "CustomerService2".
Using Java configuration based Spring bean definition I would do it this way:
@Configuration
public static class ContextConfig {
@Bean
public CustomerService customerService() {
if (System.getProperty("servicedefault")!=null) {
return new CustomerServiceImpl1();
}
return new CustomerServiceImpl2();
}
}
An alternate approach is to use Spring Bean Profiles introduced with Spring 3.1:
@Bean
@Profile("default")
public CustomerService service1() {
return new CustomerServiceImpl1();
}
@Bean
@Profile("prod")
public CustomerService service2() {
return new CustomerServiceImpl2();
}
However, Profiles in this specific instance is a bit unwieldy as it will be difficult to set a profile for managing the implementation strategy of one bean, it is much more appropriate for cases where the behavior for a set of beans needs to be controlled.
Spring 4 introduces Conditional annotation where this behavior can be achieved in a little more reusable way.
Conditional depends on a set of Condition classes to specify the predicate, this way:
class HardCodedSystemPropertyPresentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return (System.getProperty("servicedefault") != null);
}
}
class HardCodedSystemPropertyAbsentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return (System.getProperty("servicedefault") == null);
}
}
I need two predicates, one to specify the positive condition and one to specify the negative condition, these can now be applied on the bean definitions:
@Bean
@Conditional(HardCodedSystemPropertyPresentCondition.class)
public CustomerService service1() {
return new CustomerServiceImpl1();
}
@Bean
@Conditional(HardCodedSystemPropertyAbsentCondition.class)
public CustomerService service2() {
return new CustomerServiceImpl2();
}
However, note that there is a hardcoded system property name "servicedefault" in the code of the Condition, this can be cleaned up further by using meta annotations. A new meta annotation can be defined this way:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
public String value();
public boolean exists() default true;
}
This meta annotation ConditionalOnSystemProperty takes in two user specified attributes - "value" for the system property name and "exists" to check whether the property exists or to check that the property does not exist. The meta annotation is tagged with @Conditional annotation which points to the Condition class to trigger for beans annotated with this new meta annotation, the Condition class is the following:
public class OnSystemPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes
= metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
Boolean systemPropertyExistsCheck = (Boolean)attributes.get("exists");
String systemProperty = (String)attributes.get("value");
if ((systemPropertyExistsCheck && (System.getProperty(systemProperty) != null)) ||
(!systemPropertyExistsCheck && (System.getProperty(systemProperty) == null))) {
return true;
}
return false;
}
}
The logic here is to get hold of the attributes defined on the @Bean instances using the meta-annotation, and to trigger the check for the presence or the absence of the system property based on the additional "exists" attribute. This reusable meta-annotation can now be defined on the @Bean instances to conditionally create the beans, this way:
@Configuration
public static class ContextConfig {
@Bean
@ConditionalOnSystemProperty("servicedefault")
public CustomerService service1() {
return new CustomerServiceImpl1();
}
@Bean
@ConditionalOnSystemProperty(value="servicedefault", exists=false)
public CustomerService service2() {
return new CustomerServiceImpl2();
}
}
Wrap Up
The example here is trivial and probably not very realistic and is used purely to demonstrate the Conditional feature. A far better example in Spring 4 is the way Conditional is used for modifying the behavior of Spring 3.1 based Profiles itself that I had mentioned previously, Profiles is internally now based on meta-annotation based Conditional:
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}