How to conditionally declare Bean when multiple profiles are not active?
SpringSpring BootSpring Problem Overview
In my Spring-Boot-App I want to conditionally declare a Bean, depending on (un)loaded spring-profiles.
The conditon:
Profile "a" NOT loaded
AND
Profile "b" NOT loaded
My solution so far (which works):
@Bean
@ConditionalOnExpression("#{!environment.getProperty('spring.profiles.active').contains('a') && !environment.getProperty('spring.profiles.active').contains('b')}")
public MyBean myBean(){/*...*/}
Is there a more elegant (and shorter) way to explain this condition?
Especially I want to get rid of the usage of Spring Expression Language here.
Spring Solutions
Solution 1 - Spring
Since Spring 5.1.4 (incorporated in Spring Boot 2.1.2) it is possible to use a profile expression inside profile string annotation. So:
In Spring 5.1.4 (Spring Boot 2.1.2) and above it is as easy as:
@Component
@Profile("!a & !b")
public class MyComponent {}
In Spring 4.x and 5.0.x:
There are many approaches for this Spring versions, each one of them has its pro's and con's. When there aren't many combinations to cover I personally like @Stanislav answer with the @Conditional
annotation.
Other approaches can be found in this similar questions:
Spring Profile - How to include AND condition for adding 2 profiles?
Solution 2 - Spring
If you have a single profile you could simply use a @Profile
annotation with the not operator. It also accepts multiple profiles, but with the OR
condition.
So, the alternative solution is to use a custom Condition
with the @Conditional
annotation. Like so:
public class SomeCustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Return true if NOT "a" AND NOT "b"
return !context.getEnvironment().acceptsProfiles("a")
&& !context.getEnvironment().acceptsProfiles("b");
}
}
And then annotate your method with it, like:
@Bean
@Conditional(SomeCustomCondition.class)
public MyBean myBean(){/*...*/}
Solution 3 - Spring
I prefer this solution which is more verbose but still fine for two profiles only:
@Profile("!a")
@Configuration
public class NoAConfig {
@Profile("!b")
@Configuration
public static class NoBConfig {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
}
Solution 4 - Spring
Unfortunately I don't have a shorter solution for you, but if it is suitable in your case to create the same beans for each profile, you may consider the following approach.
@Configuration
public class MyBeanConfiguration {
@Bean
@Profile("a")
public MyBean myBeanForA() {/*...*/}
@Bean
@Profile("b")
public MyBean myBeanForB() {/*...*/}
@Bean
@ConditionalOnMissingBean(MyBean.class)
public MyBean myBeanForOthers() {/*...*/}
}
Solution 5 - Spring
A variant of @Stanislav answer which is a tiny bit more readable, for Spring versions up to 5.0.x / Spring Boot 2.0.x.
public class SomeCustomCondition implements Condition {
@Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
final Environment environment = context.getEnvironment();
// true if profile is NOT "a" AND NOT "b" AND NOT "c"
return !environment.acceptsProfiles("a", "b", "c");
}
}
For newer versions of Spring / Spring Boot, see the f-CJ answer.