2014-11-21 8 views
25

L'annotazione Spring Profile consente di selezionare i profili. Tuttavia, se leggi la documentazione, ti consente solo di selezionare più di un profilo con l'operazione OR. Se si specifica @Profile ("A", "B"), il bean sarà attivo se il profilo A o il profilo B sono attivi.Spring: come fare AND in Profili?

Il nostro caso d'uso è diverso, vogliamo supportare le versioni TEST e PROD di più configurazioni. Pertanto a volte desideriamo rendere autowire il bean solo se entrambi i profili TEST e CONFIG1 sono attivi.

C'è un modo per farlo con Spring? Quale sarebbe il modo più semplice?

+1

beh in docs è menzionato come comportamento 'e/o' per' @Profile ("a", "b") '. Non è quello che stai cercando? docs - 'Allo stesso modo, se una classe @Component o @Configuration è contrassegnata con @Profile ({" p1 "," p2 "}), quella classe non sarà registrata/elaborata a meno che i profili 'p1' e/o 'p2' non abbiano stato attivato. –

+0

@JavaBond che significa che è l'operatore "OR" e non "AND". Volevano solo sottolineare esplicitamente che non è esclusivo o (xor) – Artem

+0

Ho aperto un ticket per Spring Source per supportare l'operatore "AND" per l'annotazione del profilo: https://jira.spring.io/browse/SPR-12458 – Artem

risposta

19

Poiché Spring non fornisce la funzionalità AND fuori dalla scatola. Vorrei suggerire la seguente strategia:

Attualmente l'annotazione @Profile ha un'annotazione condizionale @Conditional(ProfileCondition.class). In ProfileCondition.class itera attraverso i profili e controlla se il profilo è attivo. Allo stesso modo è possibile creare la propria implementazione condizionale e limitare la registrazione del bean. per esempio.

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

} 

Nella classe:

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

NOTA: Non ho controllato per tutti i casi d'angolo (come null, controlli lunghezza ecc). Ma questa direzione potrebbe aiutare. versione migliorata

+0

e come ottenere lo stesso risultato con la configurazione xml? –

+1

Grazie! Migliorato un po 'il tuo codice nella mia risposta. –

7

Un po 'di @Mithun risposta:

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

} 

era in grado di postare nei commenti.

5

Se si è già segnato una classe di configurazione o di un metodo di fagioli con @profile annotazione, è semplice per verificare la presenza di profili aggiuntivi (ad esempio per la condizione E) con Environment.acceptsProfiles()

@Autowired Environment env; 

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

Un'altra opzione è quella di giocare il livello Classe/Metodo consentito dall'annotazione @Profile. Non flessibile come l'implementazione di MyProfileCondition ma veloce e pulito se adatto al tuo caso.

ad es. questo non si avvierà quando VELOCE & DEV sono entrambe attive, ma lo farà se solo DEV è:

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

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

ho migliorato la risposta di @ rozhoc dal momento che la risposta non tiene conto del fatto che nessun profilo è equivalente a 'default' quando si tratta di usare @Profile. Inoltre, le condizioni che volevo erano !default && !a che il codice di @ rozhoc non gestiva correttamente. Finalmente ho usato un po 'di Java8 e mostravo solo il metodo matches per brevità.

@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; 
} 

Ecco come effettivamente utilizzare nel caso in cui era confusa così:

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

rozhok, non rozhoc pls. –

+0

Ehi, quello ha funzionato. Vincente! – sbzoom

2

Un altro tipo di trucco, ma potrebbe funzionare in molti scenari è messo l'annotazione @profile sul @Configuration e l'altro @Profile su @Bean - che crea AND logico tra 2 profili nella configurazione di primavera basata su java.

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

    @Bean 
    @Profile("Profile2") 
    public EmbeddedServletContainerCustomizer containerCustomizer() {