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:
1 2 3 4 5 6 7 8 9 10 11 | @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:
1 2 3 4 5 6 7 8 9 10 11 | @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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:
1 2 3 4 5 6 7 8 9 10 11 | @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:
1 2 3 4 5 6 7 | @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @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:
1 2 3 4 | @Conditional (ProfileCondition. class ) public @interface Profile { String[] value(); } |