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(); }
Thanks, very helpful.
ReplyDeleteSupper, please add CustomerService class to post
ReplyDeleteVery helpfull concise article. Also demonstrates a good use of a meta-annotation. Thanks.
ReplyDeleteThe code ?
ReplyDelete