【问题标题】:Spring boot 2.1 bean override vs. PrimarySpring Boot 2.1 bean 覆盖与 Primary
【发布时间】:2019-04-07 22:02:32
【问题描述】:

默认使用Spring Boot 2.1 bean overriding is disabled,这是一件好事。

但是,我确实有一些测试,我使用 Mockito 将 bean 替换为模拟实例。使用默认设置,这种配置的测试将由于 bean 覆盖而失败。

我发现唯一可行的方法是通过应用程序属性启用 bean 覆盖:

spring.main.allow-bean-definition-overriding=true

但是我真的很想确保为我的测试配置设置最少的 bean 定义,spring 会在禁用覆盖的情况下指出这一点。

我要覆盖的 bean 要么是

  • 在导入到我的测试配置中的另一个配置中定义
  • 通过注解扫描自动发现 bean

我的想法应该在覆盖 bean 的测试配置中工作,并在其上添加 @Primary,就像我们习惯于数据源配置一样。然而,这没有任何效果,让我想知道:@Primary 和禁用的 bean 是否相互矛盾?

一些例子:

package com.stackoverflow.foo;
@Service
public class AService {
}

package com.stackoverflow.foo;
public class BService {
}

package com.stackoverflow.foo;
@Configuration
public BaseConfiguration {
    @Bean
    @Lazy
    public BService bService() {
        return new BService();
    }
}

package com.stackoverflow.bar;
@Configuration
@Import({BaseConfiguration.class})
public class TestConfiguration {
    @Bean
    public BService bService() {
        return Mockito.mock(BService.class);
    }
}

【问题讨论】:

  • 与其为您自己的配置提供模拟,不如使用@MockBean 并让Spring Boot 进行替换。所以不要在你的代码中使用@Autowired BService bService@MockBean BService bService。节省您维护仅用于测试的配置。
  • 上面 M. Deinum 的评论确实解决了这个问题,但只有当你实际使用模拟时。当你需要在测试中覆盖一个 bean 时——它没有帮助。
  • 请注意,在特定测试中使用 @MockBean 会导致创建新的上下文(而不是重用缓存的上下文)并导致测试运行速度变慢。

标签: java spring spring-boot


【解决方案1】:

我使测试 bean 仅在 test 配置文件中可用,并允许仅在测试时覆盖,如下所示:

@ActiveProfiles("test")
@SpringBootTest(properties = {"spring.main.allow-bean-definition-overriding=true"})
class FooBarApplicationTests {

  @Test
  void contextLoads() {}
}

我在测试配置中模拟的 bean:

@Profile("test")
@Configuration
public class FooBarApplicationTestConfiguration {
  @Bean
  @Primary
  public SomeBean someBean() {
    return Mockito.mock(SomeBean.class);
  }
}

【讨论】:

    【解决方案2】:

    spring.main.allow-bean-definition-overriding=true 可以放在测试配置中。如果您需要广泛的集成测试,您将需要在某些时候覆盖 bean。这是不可避免的。

    虽然已经提供了正确的答案,但这意味着您的 bean 将具有不同的名称。因此,从技术上讲,这不是覆盖。

    如果您需要真正的覆盖(因为您使用 @Qualifiers@Resources 或类似的东西),因为 Spring Boot 2.X 只能使用 spring.main.allow-bean-definition-overriding=true 属性。

    更新: 小心 Kotlin Bean 定义 DSL。在 Spring Boot 中,它需要一个自定义的 ApplicationContextInitializer,如下所示:

    class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {
    
        override fun initialize(context: GenericApplicationContext) =
                beans.initialize(context)
    
    }
    

    现在,如果您决定通过 @Primary @Bean 方法在您的测试中覆盖此类基于 DSL 的 bean 之一,它不会这样做。初始化程序将在@Bean 方法之后启动,即使在测试@Bean 中使用@Primary,您仍然会在测试中获得基于DSL 的初始bean。 另一种选择是为您的测试创建一个测试初始化​​程序并将它们全部列在您的测试属性中,就像这样(顺序很重要):

    context:
        initializer:
            classes: com.yuranos.BeansInitializer, com.yuranos.TestBeansInitializer
    

    Bean 定义 DSL 还支持主要属性通过:

    bean(isPrimary=true) {...}
    

    - 当您尝试注入 bean 时,您需要消除歧义,但是如果您采用纯 DSL 方式,则不需要 main:allow-bean-definition-overriding: true

    (Spring Boot 2.1.3)

    【讨论】:

      【解决方案3】:

      覆盖 bean 意味着上下文中可能只有一个具有唯一名称或 id 的 bean。所以你可以通过以下方式提供两个bean:

      package com.stackoverflow.foo;
      @Configuration
      public class BaseConfiguration {
         @Bean
         @Lazy
         public BService bService1() {
             return new BService();
         }
      }
      
      package com.stackoverflow.bar;
      @Configuration
      @Import({BaseConfiguration.class})
      public class TestConfiguration {
          @Bean
          public BService bService2() {
              return Mockito.mock(BService.class);
          }
      }
      

      如果你添加@Primary,那么primary bean会被默认注入到:

      @Autowired
      BService bService;
      

      【讨论】:

      • 嗨,对我来说它也不起作用。如果BaseConfiguration 不是公开的,而是包私有的,你能做什么?
      【解决方案4】:

      默认允许用@Bean 覆盖@Component。你的情况

      @Service
      public class AService {
      }
      
      @Component
      public class BService {
          @Autowired
          public BService() { ... }
      }
      
      @Configuration
      @ComponentScan
      public BaseConfiguration {
      }
      
      @Configuration
      // WARNING! Doesn't work with @SpringBootTest annotation
      @Import({BaseConfiguration.class})
      public class TestConfiguration {
          @Bean // you allowed to override @Component with @Bean.
          public BService bService() {
              return Mockito.mock(BService.class);
          }
      }
      

      【讨论】:

      • 我仍然需要应用此属性:spring.main.allow-bean-definition-overriding=true 以及 @SpringBootTest(classes = {Application.class, TestConfiguration.class}) 才能使其工作。
      • @SpringBootTest 中定义TestConfiguration 类终于为我解决了问题。谢谢 Duc Tran
      猜你喜欢
      • 2019-04-05
      • 2019-11-21
      • 2017-07-30
      • 2020-06-21
      • 2020-01-05
      • 1970-01-01
      • 2019-07-16
      • 2016-12-12
      • 2015-05-01
      相关资源
      最近更新 更多