【问题标题】:Spring: How to do AND in Profiles?Spring:如何在 Profiles 中做 AND?
【发布时间】:2015-01-19 05:27:55
【问题描述】:

Spring Profile 注释允许您选择配置文件。但是,如果您阅读文档,它只允许您使用 OR 操作选择多个配置文件。如果您指定@Profile("A", "B"),那么如果配置文件 A 或配置文件 B 处于活动状态,您的 bean 就会启动。

我们的用例不同,我们希望支持多种配置的 TEST 和 PROD 版本。因此,有时我们希望仅在配置文件 TEST 和 CONFIG1 都处于活动状态时才自动装配 bean。

Spring 有什么办法吗?最简单的方法是什么?

【问题讨论】:

  • 在文档中它被称为and/or@Profile("a","b") 的行为。这不是你要找的吗?文档 - Likewise, if a @Component or @Configuration class is marked with @Profile({"p1", "p2"}), that class will not be registered/processed unless profiles 'p1' and/or 'p2' have been activated.
  • @JavaBond 这意味着它是“OR”运算符而不是“AND”。他们只是想明确指出它不是排他性的或(xor)
  • 我为 Spring Source 开了一张票,以支持 Profile 注释的“AND”运算符:jira.spring.io/browse/SPR-12458
  • 好吧。让我们看看 Spring 团队怎么说。
  • 他们接受了票,并且显然会在某个时候这样做。

标签: java spring spring-profiles


【解决方案1】:

自 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:answered by @Mithun,它完美地涵盖了当您使用他的 Condition 类实现对 Spring Bean 进行注释时,在您的配置文件注释中将 OR 转换为 AND 的情况。但我想提供另一种没有人提出的方法,它有其优点和缺点。

  • 方法 2: 只需使用@Conditional 并根据需要创建尽可能多的Condition 实现。它的缺点是必须创建与组合一样多的实现,但如果你没有很多组合,在我看来,它是一个更简洁的解决方案,它提供了更大的灵活性和实现更复杂逻辑解决方案的机会。

方法2的实现如下。

你的 Spring Bean:

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

使用这种方法,您可以轻松涵盖更复杂的逻辑情况,例如:

(测试和配置 1)| (测试和配置3)

只是想为您的问题提供更新的答案并补充其他答案。

【讨论】:

  • 似乎@Profile("TEST & CONFIG1") 表达式在 Spring 5.1 的 bean(方法)级别上还不起作用。
  • @agodinhost 它必须在班级级别
  • 是的,我在方法级别使用了@ConditionalOnExpression,使用了表达式“#{environment.acceptsProfiles('spring') && environment.acceptsProfiles('oracle')}”,它的工作率为 100%。在方法级别也有这个配置文件表达式会非常好。恕我直言,在方法级别有所不同是没有意义的。
  • 只是一个小小的更新,现在不推荐使用 AcceptProfiles(Profiles.of("TEST")) 作为 String [] 的重载。
【解决方案2】:

另一种技巧,但可能在许多情况下都可以使用,将@Profile 注释放在@Configuration 上,将另一个@Profile 放在@Bean 上——这会在基于 java 的 spring 配置中的 2 个配置文件之间创建逻辑与。

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

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

【讨论】:

  • 我觉得这个比上面的更干净。
【解决方案3】:

我改进了@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})

【讨论】:

  • rozhok,不是 rozhoc 请。
【解决方案4】:

另一个选项是在@Profile 注释允许的类/方法级别上运行。不像实现MyProfileCondition 那样灵活,但如果适合您的情况,它又快又干净。

例如当 FAST 和 DEV 都处于活动状态时,这不会启动,但如果只有 DEV 是:

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

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

【讨论】:

    【解决方案5】:

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

    无法在 cmets 中发布。

    【讨论】:

      【解决方案6】:

      如果您已经使用 @Profile 注释标记了配置类或 bean 方法,则使用 Environment.acceptsProfiles() 检查其他配置文件(例如 AND 条件)很简单

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

      【讨论】:

        【解决方案7】:

        由于 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、长度检查等)。但是,这个方向可能会有所帮助。

        【讨论】:

        • 以及如何通过 xml 配置完成相同的操作?
        • 谢谢!在我的回答中对您的代码进行了一些改进。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-08
        • 1970-01-01
        • 1970-01-01
        • 2020-03-30
        • 2014-04-16
        相关资源
        最近更新 更多