2014-11-21 22 views
25

Adnotacja wiosenna Profile umożliwia wybieranie profili. Jednak jeśli czytasz dokumentację, możesz tylko wybrać więcej niż jeden profil z operacją OR. Jeśli wybierzesz @Profile ("A", "B"), twój komponent będzie wyświetlany, jeśli aktywny jest profil A lub profil B.Wiosna: jak zrobić AND w profilach?

Nasz przypadek użycia jest inny, chcemy wspierać wersje TEST i PROD o wielu konfiguracjach. Dlatego czasami chcemy automatycznie wywoływać komponenty bean tylko wtedy, gdy oba profile TEST i CONFIG1 są aktywne.

Czy jest jakiś sposób na zrobienie tego wiosną? Jaki byłby najprostszy sposób?

+1

również w dokumentach wspomniane jest jako zachowanie 'i/lub' dla '@Profile (" a "," b ")'. Czy tego nie szukasz? docs - "Podobnie, jeśli klasa @Component lub @Configuration jest oznaczona @Profile ({" p1 "," p2 "}), ta klasa nie zostanie zarejestrowana/przetworzona, chyba że profile" p1 "i/lub" p2 "mają został aktywowany. " –

+0

@JavaBond co oznacza, że ​​jest operatorem" OR ", a nie" AND ". Chcieli tylko wyraźnie zaznaczyć, że nie jest to wyłączne lub (Xor) – Artem

+0

Otworzyłem bilet do Źródła Wiosny, aby obsługiwać operator "AND" dla opisu profilu: https://jira.spring.io/browse/SPR-12458 – Artem

Odpowiedz

19

Ponieważ wiosna nie zapewnia funkcji AND po wyjęciu z pudełka. Sugeruję następującą strategię:

Obecnie adnotacja @Profile ma adnotację warunkową @Conditional(ProfileCondition.class). W ProfileCondition.class iteruje on przez profile i sprawdza, czy profil jest aktywny. Podobnie możesz stworzyć własną warunkową implementację i ograniczyć rejestrację fasoli. na przykład

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

} 

W swojej klasie:

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

UWAGA: Nie sprawdzałem wszystkich przypadkach narożnych (jak null, sprawdza długość itp). Ale ten kierunek może pomóc.

+0

i jak osiągnąć to samo z konfiguracją xml? –

+1

Dzięki! Poprawiłem trochę twój kod w mojej odpowiedzi. –

7

Nieco poprawiona wersja @Mithun odpowiedź:

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

} 

był w stanie pisać w komentarzach.

5

Jeśli już zaznaczono klasy lub metody konfiguracji Bean z @Profile adnotacji, to jest proste do sprawdzenia dodatkowych profili (np AND warunek) z Environment.acceptsProfiles()

@Autowired Environment env; 

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

Jeszcze innym rozwiązaniem jest grać na poziom klasy/metody dozwolony przez adnotację @Profile. Nie jest tak elastyczny jak implementacja MyProfileCondition, ale szybki i czysty, jeśli pasuje do twojego przypadku.

np. nie rozpocznie się, gdy FAST & DEV są aktywne, ale jeśli tylko DEV jest:

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

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

poprawiłem odpowiedź @ rozhoc od czasu, że odpowiedź nie uwzględnia faktu, że nie ma żadnego profilu jest odpowiednikiem „domyślnie” jeśli chodzi o używanie @Profile. Warunki, o które chciałem, to: !default && !a, których kod @ rozhoc nie obsługuje poprawnie. W końcu użyłem trochę Java8 i pokażę tylko metodę matches dla zwięzłości.

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

Oto jak właściwie go używać w przypadku, gdy było niejasne, a także:

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

rozhok, nie rozhoc pls. –

+0

Hej, który działał. Zwycięski! – sbzoom

2

Innym rodzajem sztuczki, ale może pracować w wielu sytuacjach jest umieścić @Profile adnotacji na @Configuration a druga @Profile na @Bean - który tworzy logiczne ORAZ pomiędzy 2 profilami w konfiguracji źródłowej opartej na Java.

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

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