当多个配置文件未激活时,如何有条件地声明Bean?

80
在我的Spring Boot应用程序中,我希望根据已(未)加载的Spring配置文件条件性地声明一个Bean。
条件是:
Profile "a" NOT loaded  
AND  
Profile "b" NOT loaded 

目前我的解决方案(已经起作用):

@Bean
@ConditionalOnExpression("#{!environment.getProperty('spring.profiles.active').contains('a') && !environment.getProperty('spring.profiles.active').contains('b')}")
    public MyBean myBean(){/*...*/}

有没有更优雅(且更短)的方法来解释这个条件?
我特别想摆脱在这里使用Spring表达式语言。

5个回答

138

自从Spring 5.1.4(包含在Spring Boot 2.1.2中)起,可以在profile string注释中使用profile表达式。因此:

Spring 5.1.4 (Spring Boot 2.1.2) 及以上版本中,只需这样:

@Component
@Profile("!a & !b")
public class MyComponent {}

Spring 4.x 和 5.0.x 中:

有许多方法适用于这些版本的Spring,每个方法都有其优缺点。当没有太多组合需要覆盖时,我个人喜欢使用带有@Conditional注释的@Stanislav答案

其他方法可以在以下类似问题中找到:

Spring Profile - 如何包含AND条件以添加2个配置文件?

Spring: 如何在配置文件中使用AND?


1
这太棒了!现在应该接受这个答案 :-) - Mike Boddin
1
对于这种特殊情况(双重否定),您需要特别确保它是 spring-core: 5.1.4+ / spring-boot: 2.1.2+,因为在那些版本之前存在一个令人讨厌的 bug:https://github.com/spring-projects/spring-framework/issues/22138。 - icyerasor
这个能有超过两个的配置文件吗?我相信应该可以正常工作。例如: "!a & !b & !c" - vermachint

37

如果您只有一个配置文件,可以使用带有非运算符的@Profile注释。它也可以接受多个配置文件,但是使用OR条件。

因此,替代解决方案是使用自定义Condition@Conditional注释。方法如下:

public class SomeCustomCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

    // Return true if NOT "a" AND NOT "b"
    return !context.getEnvironment().acceptsProfiles("a") 
                  && !context.getEnvironment().acceptsProfiles("b");
  }
}

然后在你的方法上加上注释,如下:

@Bean
@Conditional(SomeCustomCondition.class)
public MyBean myBean(){/*...*/}

1
谢谢,我喜欢这种方法。使用Condition给你点赞。在我接受这个答案之前,我会等一段时间的。也许还有更优雅的建议出现;-)(但我不这样认为)。 - Mike Boddin
1
我尝试了与上面相同的方法,但由于某些原因它忽略了@Conditional注释。有什么建议吗? - Mind Peace
acceptProfiles() 是一个可变参数。我们可以一次指定多个配置文件,例如 return !context.getEnvironment().acceptsProfiles("a", "b") ; - Ashutosh Srivastav

21

我更喜欢这个解决方案,虽然更冗长,但对于仅有两个配置文件来说仍然很好:

@Profile("!a")
@Configuration
public class NoAConfig {

    @Profile("!b")
    @Configuration
    public static class NoBConfig {
        @Bean
        public MyBean myBean(){
            return new MyBean();
        }
    }

}

18

很遗憾,我没有更短的解决方案提供给你。但如果在你的情况下为每个配置文件创建相同的bean是可行的,那么你可以考虑以下方法。

@Configuration
public class MyBeanConfiguration {

   @Bean
   @Profile("a")
   public MyBean myBeanForA() {/*...*/}

   @Bean
   @Profile("b")
   public MyBean myBeanForB() {/*...*/}

   @Bean
   @ConditionalOnMissingBean(MyBean.class)
   public MyBean myBeanForOthers() {/*...*/}

}

谢谢,但这对我来说不合适。假设我们加载了配置文件ab,那么在这种情况下MyBean会被加载两次,通常我们不希望出现这种行为。 另外一点是,这并不是我问题的解���方案。当两个配置文件都没有加载时,MyBean也不应该被声明。 - Mike Boddin
5
像这样使用@ConditionalOnMissingBean是很危险的。为了安全起见,它应该仅用于自动配置类。 - Andy Wilkinson
谢谢你们的评论,伙计们。我过去常常使用配置文件作为一堆与环境相关的配置属性,比如“dev”、“test”等。实际上,如果同时加载了两个配置文件,这种方法是行不通的。 - dmytro
@AndyWilkinson,您能否解释一下为什么这种方法是不安全的? - wakedeer
@wakedeer 如果没有自动配置提供的排序保证,那么对于myBeanForOthers的条件可能会在另一个也定义了类型为MyBean的bean的@Bean方法之前被评估。这将使您拥有两个类型为MyBean的bean。 - Andy Wilkinson

3

这是 @Stanislav答案 的一种变体,更易于阅读。适用于Spring版本在5.0.x及以下/Spring Boot 2.0.x。

public class SomeCustomCondition implements Condition {
  @Override
  public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
    final Environment environment = context.getEnvironment();

    // true if profile is NOT "a" AND NOT "b" AND NOT "c"
    return !environment.acceptsProfiles("a", "b", "c");
  }
}

对于较新的Spring / Spring Boot版本,请参见f-CJ答案


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接