Spring Boot 2.1 bean 覆盖与 Primary

Posted

技术标签:

【中文标题】Spring Boot 2.1 bean 覆盖与 Primary【英文标题】:Spring boot 2.1 bean override vs. 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.***.foo;
@Service
public class AService 


package com.***.foo;
public class BService 


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


package com.***.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 会导致创建新的上下文(而不是重用缓存的上下文)并导致测试运行速度变慢。 【参考方案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.***.foo;
@Configuration
public class BaseConfiguration 
   @Bean
   @Lazy
   public BService bService1() 
       return new BService();
   


package com.***.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

以上是关于Spring Boot 2.1 bean 覆盖与 Primary的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot:@TestConfiguration 在集成测试期间不覆盖 Bean

Spring Boot:在单元测试中用一个覆盖多个bean

Spring Boot 2.1 和 Java 11 中的 Bean 生命周期

如何在不覆盖 Spring Boot 使用的情况下定义自定义 ObjectMapper bean

Spring Boot Actuator [监控与管理]

Spring Boot - 不覆盖服务器端口属性