HOW-TO: Register components using @Conditional and Condition in Spring
@Profile
annotation in Spring can be used on any Spring components (e.g. @Component
, @Service
, @Configuration
etc.) that are candidates for auto-detection. @Profile
annotation accepts a single profile or a set of profiles that must be active in order to make the annotated component eligible for auto-detection. For a given @Profile({"p1", "!p2"})
, registration will occur if profile p1
is active or if profile p2
is not active. The or is crucial here.
But how to achieve this with @Profile
: we want to activate a given component if profile p1
is active and if profiles p2
and p3
are both inactive?
Let’s assume the following situation: we have aNotificationSender
interface that is implemented by:
SendGridNotificationSender
– active only ifsendgrid
profile is active,EmailNotificationSender
– active only ifemail
profile is active.NoOpNotificationSender
– active only ifdevelopment
profile is active and none ofsendgrid
andemail
are active.
In addition: only one NotificationSender
can be registered at a time and development
profile can be used in conjunction with sendgrid
and email
profiles.
In the above situation using the @Profile
annotation seems not enough. Maybe I am complicating things a bit, but actually I really wanted to achieve the above without introducing another profile. And how I did it?
I used Spring’s 4 @Conditional
annotation. @Conditional
allow registration of component when all specified Condition
(s) match:
@Component @Conditional(value = NoOpNotificationSender.ProfilesCondition.class) class NoOpNotificationSender extends NotificationSenderAdapter { }
ProfilesCondition
implements org.springframework.context.annotation.Condition
interface:
public static class ProfilesCondition implements Condition { @Override public boolean matches(ConditionContext c, AnnotatedTypeMetadata m) { } }
The whole solution to the problem:
@Component @Conditional(value = NoOpNotificationSender.ProfilesCondition.class) class NoOpNotificationSender extends NotificationSenderAdapter { static class ProfilesCondition implements Condition { @Override public boolean matches(ConditionContext c, AnnotatedTypeMetadata m) { return accepts(c, Profiles.DEVELOPMENT) && !accepts(c, Profiles.MAIL) && !accepts(c, Profiles.SEND_GRID); } private boolean accepts(ConditionContext c, String profile) { return c.getEnvironment().acceptsProfiles(profile); } } }
The other components will be activated when appropriate profile is active:
@Component @Profile(value = Profiles.SEND_GRID) public class SendGridNotificationSender extends NotificationSenderAdapter { } @Component @Profile(value = Profiles.MAIL) class EmailNotificationSender extends NotificationSenderAdapter { }
Usage examples:
Active profiles | Bean |
---|---|
development | NoOpNotificationSender |
development,sendgrid | SendGridNotificationSender |
development,mail | EmailNotificationSender |
sendgrid | SendGridNotificationSender |
EmailNotificationSender |
What do you think? How would you solve this problem?
Reference: | HOW-TO: Register components using @Conditional and Condition in Spring from our JCG partner Rafal Borowiec at the Codeleak.pl blog. |