简体   繁体   English

Spring:如何在Profiles中做AND?

[英]Spring: How to do AND in Profiles?

Spring Profile annotation allows you to select profiles. Spring Profile注释允许您选择配置文件。 However if you read documentation it only allows you to select more than one profile with OR operation. 但是,如果您阅读文档,它只允许您使用OR操作选择多个配置文件。 If you specify @Profile("A", "B") then your bean will be up if either profile A or profile B is active. 如果指定@Profile(“A”,“B”),那么如果配置文件A或配置文件B处于活动状态,则bean将启动。

Our use case is different we want to support TEST and PROD versions of multiple configurations. 我们的用例不同,我们希望支持多种配置的TEST和PROD版本。 Therefore sometimes we want to autowire the bean only if both profiles TEST and CONFIG1 are active. 因此,有时我们只想在配置文件TEST和CONFIG1都处于活动状态时自动装配bean。

Is there any way to do it with Spring? 有没有办法用Spring做到这一点? What would be the simplest way? 什么是最简单的方法?

Since Spring does not provide the AND feature out of the box. 由于Spring不提供开箱即用的AND功能。 I would suggest the following strategy: 我会建议以下策略:

Currently @Profile annotation has a conditional annotation @Conditional(ProfileCondition.class) . 目前@Profile注释具有条件注释@Conditional(ProfileCondition.class) In ProfileCondition.class it iterates through the profiles and checks if the profile is active. ProfileCondition.class它遍历配置文件并检查配置文件是否处于活动状态。 Similarly you could create your own conditional implementation and restrict registering the bean. 类似地,您可以创建自己的条件实现并限制注册bean。 eg 例如

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

}

In your class: 在你的班上:

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

NOTE: I have not checked for all the corner cases (like null, length checks etc). 注意:我没有检查所有角落情况(如空值,长度检查等)。 But, this direction could help. 但是,这个方向可能有所帮助。

Since Spring 5.1 (incorporated in Spring Boot 2.1) it is possible to use a profile expression inside profile string annotation. 从Spring 5.1(包含在Spring Boot 2.1中)开始,可以在配置文件字符串注释中使用配置文件表达式。 So: 所以:

In Spring 5.1 (Spring Boot 2.1) and above it is as easy as: Spring 5.1(Spring Boot 2.1)及以上版本中,它很简单:

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

Spring 4.x and 5.0.x : Spring 4.x和5.0.x

  • Approach 1: answered by @Mithun , it covers perfectly your case of converting OR into AND in your profile annotation whenever you annotate the Spring Bean also with his Condition class implementation. 方法1: 由@Mithun回答 ,它完全涵盖了在您使用Condition类实现注释Spring Bean时将OR转换为AND的情况。 But I want to offer another approach that nobody proposed that has its pro's and con's. 但我想提供另一种方法,没有人提出它有它的专业和骗局。

  • Approach 2: Just use @Conditional and create as many Condition implementations as combinations needed. 方法2:只需使用@Conditional并根据需要创建尽可能多的Condition实现。 It has the con of having to create as many implementations as combinations but if you don't have many combinations, in my opinion, it is a more concise solution and it offers more flexibility and the chance of implementing more complex logical resolutions. 它必须创建尽可能多的组合实现,但如果你没有很多组合,在我看来,它是一个更简洁的解决方案,它提供了更多的灵活性和实现更复杂的逻辑分辨率的机会。

The implementation of Approach 2 would be as follows. 方法2的实施如下。

Your Spring Bean: 你的春豆:

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

TestAndConfig1Profiles implementation: 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");
    }
}

With this approach you could easily cover more complex logical situations like for example: 使用这种方法,您可以轻松覆盖更复杂的逻辑情况,例如:

(TEST & CONFIG1) | (TEST&CONFIG1)| (TEST & CONFIG3) (TEST&CONFIG3)

Just wanted to give an updated answer to your question and complement other answers. 只是想给出一个更新的问题答案,并补充其他答案。

A little bit improved version of @Mithun answer: 一点点改进版的@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();
}

}

Was unable to post it in the comments. 无法在评论中发布。

Yet another option is to play on the Class/Method level allowed by the @Profile annotation. 另一个选择是播放@Profile注释所允许的类/方法级别。 Not as flexible as implementing MyProfileCondition but quick and clean if it suits your case. 不像实现MyProfileCondition那样灵活,但如果它适合您的情况则快速而干净。

eg this won't start when FAST & DEV are both active, but will if only DEV is: 例如,当FAST和DEV都处于活动状态时,这将不会启动,但如果只有DEV是:

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

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

I improved @rozhoc's answer since that answer did not account for the fact that no profile is equivalent to 'default' when it comes to using @Profile. 我改进了@ rozhoc的答案,因为答案没有说明在使用@Profile时没有配置文件等同于'default'。 Also, conditions that I wanted were !default && !a which @rozhoc's code did not handle properly. 另外,我想要的条件是!default && !a @ rozhoc的代码没有正确处理。 Finally I used some Java8 and show only the matches method for brevity. 最后我使用了一些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;
}

Here is how you actually use it in case that was confusing as well: 以下是您实际使用它的方式,以防令人困惑:

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

If you have already marked a configuration class or bean method with @Profile annotation, it is simple to check for additional profiles (eg for AND condition) with Environment.acceptsProfiles() 如果您已使用@Profile注释标记了配置类或bean方法,则可以使用Environment.acceptsProfiles()检查其他配置文件(例如,对于AND条件Environment.acceptsProfiles()

@Autowired Environment env;

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

Another kind of trick but might work in many scenarios is put @Profile annotation on @Configuration and the other @Profile on @Bean - that creates logical AND between 2 profiles in java-based spring config. 另一种技巧但可能在许多情况下都可以使用@Profile注释放在@Configuration上,另一个@Profile放在@Bean上 - 它在基于java的spring配置中的2个配置文件之间创建逻辑AND。

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM