Can I negate (!) a collection of spring profiles?

HellishHeat picture HellishHeat · Apr 2, 2017 · Viewed 11.8k times · Source

Is it possible to configure a bean in such a way that it wont be used by a group of profiles? Currently I can do this (I believe):

@Profile("!dev, !qa, !local")

Is there a neater notation to achieve this? Let's assume I have lots of profiles. Also, if I have a Mock and concrete implementation of some service (or whatever), Can I just annotate one of them, and assume the other will be used in all other cases? In other words, is this, for example, necessary:

@Profile("dev, prof1, prof2")
public class MockImp implements MyInterface {...}

@Profile("!dev, !prof1, !prof2") //assume for argument sake that there are many other profiles
public class RealImp implements MyInterface {...}

Could I just annotate one of them, and stick a @Primary annotation on the other instead?

In essence I want this:

@Profile("!(dev, prof1, prof2)")

Thanks in advance!

Answer

Anthony Raymond picture Anthony Raymond · Apr 3, 2017

Short answer is : You can't.
But there is a neat workarounds that exists thanks to the @Conditional annotation.

Create Condition matchers:

public abstract class ProfileCondition extends SpringBootCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        if (matchProfiles(conditionContext.getEnvironment())) {
            return ConditionOutcome.match("A local profile has been found.");
        }
        return ConditionOutcome.noMatch("No local profiles found.");
    }

    protected abstract boolean matchProfiles(final Environment environment);
}

public class DevProfileCondition extends ProfileCondition {
   private boolean matchProfiles(final Environment environment) {    
        return Arrays.stream(environment.getActiveProfiles()).anyMatch(prof -> {
            return prof.equals("dev") || prof.equals("prof1")) || prof.equals("prof2"));
        });
    }
}

public class ProdProfileCondition extends ProfileCondition {
   private boolean matchProfiles(final Environment environment) {    
        return Arrays.stream(environment.getActiveProfiles()).anyMatch(prof -> {
            return !prof.equals("dev") && !prof.equals("prof1")) && !prof.equals("prof2"));
        });
    }
}

Use it

@Conditional(value = {DevProfileCondition.class})
public class MockImpl implements MyInterface {...}

@Conditional(value = {ProdProfileCondition.class})
public class RealImp implements MyInterface {...}

However, this aproach requires Springboot.