web-dev-qa-db-fra.com

Puis-je annuler (!) Une collection de profils de ressort?

Est-il possible de configurer un bean de telle manière qu'il ne soit pas utilisé par un groupe de profils? Actuellement, je peux le faire (je crois):

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

Existe-t-il une notation plus nette pour y parvenir? Supposons que j'ai beaucoup de profils. De plus, si j'ai une implémentation simulée et concrète d'un service (ou autre), puis-je simplement annoter l'un d'entre eux et supposer que l'autre sera utilisé dans tous les autres cas? En d'autres termes, est-ce, par exemple, nécessaire:

@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 {...}

Puis-je simplement annoter l'un d'eux et coller un @Primary annotation de l'autre à la place?

En substance, je veux ceci:

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

Merci d'avance!

13
HellishHeat

La réponse courte est: vous ne pouvez pas.
Mais il existe une solution de contournement soignée qui existe grâce à la @Conditional annotation.

Créer des conditionneurs de conditions:

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"));
        });
    }
}

Utilise le

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

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

Cependant, cette approche nécessite Springboot.

13
Anthony Raymond

D'après ce que je comprends, ce que vous voulez faire est de pouvoir remplacer certains de vos beans par des beans stub/mock pour des profils spécifiques. Il existe 2 façons de résoudre ce problème:

  • Exclure les beans non nécessaires pour les profils correspondants et inclure par défaut tout le reste
  • Inclure uniquement les beans requis pour chaque profil

La première option est faisable mais difficile. En effet, le comportement par défaut de Spring lors de la fourniture de plusieurs profils dans l'annotation @Profile Est une condition OR (pas une AND comme vous en auriez besoin dans votre cas). Ce comportement de Spring est le plus intuitif, car idéalement chaque profil doit correspondre à chaque configuration de votre application (production, test unitaire, test d'intégration, etc.), donc un seul profil doit être actif à chaque fois. C'est la raison OR est plus logique que AND entre les profils. Par conséquent, vous pouvez contourner cette limitation, probablement en imbriquant des profils, mais vous feriez votre configuration très complexe et moins maintenable.

Ainsi, je vous suggère de suivre la deuxième approche. Ayez un profil unique pour chaque configuration de votre application. Tous les beans qui sont identiques pour chaque configuration peuvent résider dans une classe qui n'aura pas de @Profile Spécifié. En conséquence, ces beans seront instanciés par tous les profils. Pour les beans restants qui doivent être distincts pour chaque configuration différente, vous devez créer une classe @Configuration Distincte (pour chaque profil Spring), en les ayant tous avec @Profile Défini sur le profil correspondant. De cette façon, il sera très facile de traiter ce qui est injecté dans tous les cas.

Cela devrait être comme ci-dessous:

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

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

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

@Profile("the-last-profile") //you should define an additional profile, not rely on excluding as described before
public class RealImp implements MyInterface {...}

Enfin, l'annotation @Primary Est utilisée pour remplacer un beans existant. Lorsqu'il y a 2 beans du même type, s'il n'y a pas d'annotation @Primary, Vous obtiendrez une erreur d'instanciation de Spring. Si vous définissez une annotation @Primary Pour l'un des beans, il n'y aura pas d'erreur et ce bean sera injecté partout où ce type est requis (l'autre sera ignoré). Comme vous le voyez, cela n'est utile que si vous avez un seul profil. Sinon, cela deviendra également compliqué comme premier choix.

TL; DR : Oui, vous le pouvez. Pour chaque type, définissez un bean pour chaque profil et ajoutez une annotation @Profile Avec uniquement ce profil.

1
Dimos