Spring: Как сделать AND в профилях?

Аннотации профиля Spring позволяют вам выбирать профили. Однако, если вы читаете документацию, это позволяет вам выбирать более одного профиля при операции ИЛИ. Если вы укажете @Profile ("A", "B"), ваш компонент будет активен, если активен либо профиль A, либо профиль B.

Наш вариант использования отличается от того, что мы хотим поддерживать версии TEST и PROD для нескольких конфигураций. Поэтому иногда мы хотим использовать autowire bean-компонент только в том случае, если активны оба профиля TEST и CONFIG1.

Есть ли способ сделать это с весной? Каким будет самый простой способ?

Ответ 1

Поскольку Spring не предоставляет функцию AND из коробки. Я бы предложил следующую стратегию:

В настоящее время аннотация @Profile имеет условную аннотацию @Conditional(ProfileCondition.class). В ProfileCondition.class он выполняет итерацию через профили и проверяет, активен ли профиль. Аналогичным образом вы можете создать свою собственную условную реализацию и ограничить регистрацию компонента. например

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

}

В вашем классе:

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

ПРИМЕЧАНИЕ. Я не проверял все угловые случаи (например, нулевые, длинные проверки и т.д.). Но это может помочь.

Ответ 2

Немного улучшенная версия ответа @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();
}

}

Не удалось опубликовать его в комментариях.

Ответ 3

Еще один вариант - играть на уровне класса/метода, разрешенном аннотацией @Profile. Не такой гибкий, как реализация MyProfileCondition но быстрый и чистый, если он подходит вашему делу.

например, это не произойдет, когда FAST & DEV активны, но если только DEV:

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

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

Ответ 4

Если вы уже отметили класс конфигурации или метод bean с аннотацией @Profile, просто проверить дополнительные профили (например, для условия AND) с помощью Environment.acceptsProfiles()

@Autowired Environment env;

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

Ответ 5

Я улучшил @rozhoc ответ, так как этот ответ не учитывал тот факт, что ни один профиль не эквивалентен "по умолчанию", когда дело доходит до использования @Profile. Кроме того, условия, которые я хотел, были !default && !a Default !default && !a A, который не поддерживал код @rozhoc. Наконец, я использовал Java8 и для краткости покажу только метод matches.

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

Вот как вы на самом деле используете его в том случае, если это сбивает с толку:

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

Ответ 6

Другой вид трюка, но может работать во многих сценариях, - это аннотация @Profile для @Configuration и другого @Profile на @Bean, который создает логическое И между двумя профилями в java-основе Spring config.

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

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