web-dev-qa-db-fra.com

Spring: Comment faire ET dans les profils?

Spring Profile l'annotation vous permet de sélectionner des profils. Cependant, si vous lisez la documentation, cela vous permet uniquement de sélectionner plusieurs profils avec l'opération OR. Si vous spécifiez @Profile ("A", "B"), votre bean sera activé si le profil A ou le profil B est actif.

Notre cas d'utilisation est différent, nous voulons prendre en charge les versions TEST et PROD de plusieurs configurations. Par conséquent, nous voulons parfois câbler automatiquement le bean uniquement si les deux profils TEST et CONFIG1 sont actifs.

Y a-t-il un moyen de le faire avec Spring? Quelle serait la manière la plus simple?

33
Artem

Depuis Spring ne fournit pas la fonctionnalité ET hors de la boîte. Je suggérerais la stratégie suivante:

Actuellement, l'annotation @Profile A une annotation conditionnelle @Conditional(ProfileCondition.class). Dans ProfileCondition.class, Il parcourt les profils et vérifie si le profil est actif. De même, vous pouvez créer votre propre implémentation conditionnelle et restreindre l'enregistrement du bean. par exemple.

public class MyProfileCondition implements Condition {

    @Override
    public boolean matches(final ConditionContext context,
            final AnnotatedTypeMetadata metadata) {
        if (context.getEnvironment() != null) {
            final MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
            if (attrs != null) {
                for (final Object value : attrs.get("value")) {
                    final String activeProfiles = context.getEnvironment().getProperty("spring.profiles.active");

                    for (final String profile : (String[]) value) {
                        if (!activeProfiles.contains(profile)) {
                            return false;
                        }
                    }
                }
                return true;
            }
        }
        return true;
    }

}

Dans votre classe:

@Component
@Profile("dev")
@Conditional(value = { MyProfileCondition.class })
public class DevDatasourceConfig

REMARQUE: Je n'ai pas vérifié tous les cas d'angle (comme les contrôles de null, de longueur, etc.). Mais, cette direction pourrait aider.

23
Mithun

Depuis Spring 5.1 (incorporé dans Spring Boot 2.1), il est possible d'utiliser une expression de profil dans l'annotation de chaîne de profil. Donc:

Dans Spring 5.1 (Spring Boot 2.1) et au-dessus , c'est aussi simple que:

@Component
@Profile("TEST & CONFIG1")
public class MyComponent {}

Spring 4.x et 5.0.x :

  • Approche 1: répondu par @Mithun , il couvre parfaitement votre cas de conversion OR en ET dans votre annotation de profil chaque fois que vous annotez le Spring Bean également avec son Condition implémentation de classe. Mais je veux proposer une autre approche que personne n'a proposée qui a ses avantages et ses inconvénients.

  • Approche 2: utilisez simplement @Conditional et créez autant d'implémentations Condition que de combinaisons nécessaires. Il a pour inconvénient de devoir créer autant d'implémentations que de combinaisons mais si vous n'avez pas beaucoup de combinaisons, à mon avis, c'est une solution plus concise et elle offre plus de flexibilité et la possibilité d'implémenter des résolutions logiques plus complexes.

L'implémentation de Approche 2 serait la suivante.

Votre haricot de printemps:

@Component
@Conditional(value = { TestAndConfig1Profiles.class })
public class MyComponent {}

TestAndConfig1Profiles la mise en oeuvre:

public class TestAndConfig1Profiles implements Condition {
    @Override
    public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().acceptsProfiles("TEST")
                    && context.getEnvironment().acceptsProfiles("CONFIG1");
    }
}

Avec cette approche, vous pourriez facilement couvrir des situations logiques plus complexes comme par exemple:

(TEST & CONFIG1) | (TEST ET CONFIG3)

Je voulais juste donner une réponse mise à jour à votre question et compléter d'autres réponses.

13
f-CJ

Une version un peu améliorée de la réponse @Mithun:

public class AndProfilesCondition implements Condition {

public static final String VALUE = "value";
public static final String DEFAULT_PROFILE = "default";

@Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() == null) {
        return true;
    }
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs == null) {
        return true;
    }
    String[] activeProfiles = context.getEnvironment().getActiveProfiles();
    String[] definedProfiles = (String[]) attrs.getFirst(VALUE);
    Set<String> allowedProfiles = new HashSet<>(1);
    Set<String> restrictedProfiles = new HashSet<>(1);
    for (String nextDefinedProfile : definedProfiles) {
        if (!nextDefinedProfile.isEmpty() && nextDefinedProfile.charAt(0) == '!') {
            restrictedProfiles.add(nextDefinedProfile.substring(1, nextDefinedProfile.length()));
            continue;
        }
        allowedProfiles.add(nextDefinedProfile);
    }
    int activeAllowedCount = 0;
    for (String nextActiveProfile : activeProfiles) {
        // quick exit when default profile is active and allowed profiles is empty
        if (DEFAULT_PROFILE.equals(nextActiveProfile) && allowedProfiles.isEmpty()) {
            continue;
        }
        // quick exit when one of active profiles is restricted
        if (restrictedProfiles.contains(nextActiveProfile)) {
            return false;
        }
        // just go ahead when there is no allowed profiles (just need to check that there is no active restricted profiles)
        if (allowedProfiles.isEmpty()) {
            continue;
        }
        if (allowedProfiles.contains(nextActiveProfile)) {
            activeAllowedCount++;
        }
    }
    return activeAllowedCount == allowedProfiles.size();
}

}

N'a pas pu le poster dans les commentaires.

7
Vladimir Rozhkov

Une autre option consiste à jouer au niveau Classe/Méthode autorisé par le @Profile annotation. Pas aussi flexible que l'implémentation de MyProfileCondition mais rapide et propre si cela convient à votre cas.

par exemple. cela ne démarre pas lorsque FAST & DEV sont tous les deux actifs, mais si seulement DEV est:

@Configuration
@Profile("!" + SPRING_PROFILE_FAST)
public class TomcatLogbackAccessConfiguration {

    @Bean
    @Profile({SPRING_PROFILE_DEVELOPMENT, SPRING_PROFILE_STAGING})
    public EmbeddedServletContainerCustomizer containerCustomizer() {
7
coolnodje

J'ai amélioré la réponse de @ rozhoc car cette réponse ne tenait pas compte du fait qu'aucun profil n'est équivalent à "par défaut" lorsqu'il s'agit d'utiliser @Profile. De plus, les conditions que je voulais étaient !default && !a que le code de @ rozhoc n'a pas géré correctement. Enfin, j'ai utilisé du Java8 et ne montre que la méthode matches pour plus de brièveté.

@Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() == null) {
        return true;
    }
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs == null) {
        return true;
    }

    Set<String> activeProfilesSet = Arrays.stream(context.getEnvironment().getActiveProfiles()).collect(Collectors.toSet());
    String[] definedProfiles = (String[]) attrs.getFirst(VALUE);
    Set<String> allowedProfiles = new HashSet<>(1);
    Set<String> restrictedProfiles = new HashSet<>(1);
    if (activeProfilesSet.size() == 0) {
        activeProfilesSet.add(DEFAULT_PROFILE);  // no profile is equivalent in @Profile terms to "default"
    }
    for (String nextDefinedProfile : definedProfiles) {
        if (!nextDefinedProfile.isEmpty() && nextDefinedProfile.charAt(0) == '!') {
            restrictedProfiles.add(nextDefinedProfile.substring(1, nextDefinedProfile.length()));
            continue;
        }
        allowedProfiles.add(nextDefinedProfile);
    }
    boolean allowed = true;
    for (String allowedProfile : allowedProfiles) {
        allowed = allowed && activeProfilesSet.contains(allowedProfile);
    }
    boolean restricted = true;
    for (String restrictedProfile : restrictedProfiles) {
        restricted = restricted && !activeProfilesSet.contains(restrictedProfile);
    }
    return allowed && restricted;
}

Voici comment vous l'utilisez en cas de confusion:

@Profile({"!default", "!a"})
@Conditional(value={AndProfilesCondition.class})
6
RubesMN

Un autre type d'astuce, mais qui pourrait fonctionner dans de nombreux scénarios, consiste à mettre l'annotation @Profile sur @Configuration et l'autre @Profile sur @Bean - qui crée un ET logique entre 2 profils dans la configuration de printemps basée sur Java.

@Configuration
@Profile("Profile1")
public class TomcatLogbackAccessConfiguration {

   @Bean
   @Profile("Profile2")
   public EmbeddedServletContainerCustomizer containerCustomizer() {
5
Martin Tlachač

Si vous avez déjà marqué une classe de configuration ou une méthode de bean avec l'annotation @Profile, il est simple de vérifier les profils supplémentaires (par exemple pour la condition AND) avec Environment.acceptsProfiles()

@Autowired Environment env;

@Profile("profile1")
@Bean
public MyBean myBean() {
    if( env.acceptsProfiles("profile2") ) {
        return new MyBean();
    }
    else {
        return null;
    }
}
5
Sergey Shcherbakov