web-dev-qa-db-ja.com

Spring:プロフィールでANDを行う方法

Spring Profile 注釈を使用すると、プロファイルを選択できます。ただし、ドキュメントを読んだ場合、OR operation。@Profile( "A"、 "B")を指定した場合、複数のプロファイルを選択できます。プロファイルAまたはプロファイルBがアクティブです。

ユースケースが異なり、複数の構成のTESTバージョンとPRODバージョンをサポートする必要があります。そのため、TESTとCONFIG1の両方のプロファイルがアクティブな場合にのみ、Beanを自動配線することがあります。

Springでそれを行う方法はありますか?最も簡単な方法は何でしょうか?

33
Artem

SpringはそのままではAND機能を提供しないためです。次の戦略を提案します。

現在、_@Profile_注釈には条件付き注釈@Conditional(ProfileCondition.class)があります。 _ProfileCondition.class_では、プロファイルを反復処理し、プロファイルがアクティブかどうかを確認します。同様に、独自の条件付き実装を作成し、Beanの登録を制限できます。例えば.

_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
_

注:すべてのコーナーケースをチェックしていません(null、長さチェックなど)。しかし、この方向は助けになるでしょう。

23
Mithun

Spring 5.1(Spring Boot 2.1に組み込まれている)以降、プロファイル文字列注釈内でプロファイル式を使用することが可能です。そう:

Spring 5.1(Spring Boot 2.1)以上では次のように簡単です:

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

Spring 4.xおよび5.0.x

  • アプローチ1: @ Mithunが回答 、Spring BeanにConditionクラスの実装ですが、長所と短所がある、誰も提案していない別のアプローチを提供したいと思います。

  • アプローチ2:@Conditionalを使用し、必要な組み合わせと同じ数のCondition実装を作成します。組み合わせと同じ数の実装を作成する必要があるという短所がありますが、多くの組み合わせがない場合、私の意見では、より簡潔なソリューションであり、より柔軟性があり、より複雑な論理解決を実装する可能性があります。

Approach 2の実装は次のようになります。

あなたの春豆:

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

TestAndConfig1Profiles実装:

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

このアプローチを使用すると、次のようなより複雑な論理的状況を簡単にカバーできます。

(TEST&CONFIG1)| (TEST&CONFIG3)

質問に対する最新の回答を提供し、他の回答を補完したかっただけです。

13
f-CJ

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

}

コメントに投稿できませんでした。

7

さらに別のオプションは、@Profile注釈。 MyProfileConditionを実装するほど柔軟ではありませんが、ケースに合う場合は迅速かつクリーンです。

例えばこれは、FASTとDEVの両方がアクティブな場合は開始されませんが、DEVのみがアクティブな場合は開始されます。

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

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

@rozhocの答えを改善しました。その答えは、@ Profileの使用に関して「デフォルト」に相当するプロファイルがないという事実を説明していなかったためです。また、私が欲しかった条件は!default && !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
RubesMN

別の種類のトリックですが、多くのシナリオで機能する可能性がありますが、@ Configurationに@Profileアノテーションを、@ Beanに他の@Profileアノテーションを追加します。

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

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

すでに@Profileアノテーションで構成クラスまたはBeanメソッドをマークしている場合、Environment.acceptsProfiles()を使用して追加のプロファイル(AND条件など)を簡単に確認できます。

@Autowired Environment env;

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