Spring Boot 2.1 Bean Override vs.Primary

时间:2018-11-04 09:12:02

标签: java spring spring-boot

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

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

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

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

但是,我真的很想确保为我的测试配置设置最小的bean定义,这将在禁用覆盖的Spring指出。

我要覆盖的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);
    }
}

4 个答案:

答案 0 :(得分:6)

覆盖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,则默认情况下将在以下位置注入主bean:

@Autowired
BService bService;

答案 1 :(得分:3)

我仅在test配置文件中提供测试Bean,并允许在测试时进行覆盖,如下所示:

@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 :(得分:2)

默认情况下允许使用@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);
    }
}

答案 3 :(得分:2)

spring.main.allow-bean-definition-overriding=true可以置于测试配置中。如果需要进行广泛的集成测试,则有时需要覆盖bean。这是不可避免的。

只想再强调一次,即使已经提供了正确的答案,这也意味着您的bean将具有不同的名称,因此从技术上讲,它不是替代。真正的替代,如果因为使用@Qualifiers@Resources或类似的东西而需要它,则只能从spring.main.allow-bean-definition-overriding=true到Spring Boot 2.X开始。

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

class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {

    override fun initialize(context: GenericApplicationContext) =
            beans.initialize(context)

}

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

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

Bean Definition DSL还通过以下方式支持主要属性:

bean(isPrimary=true) {...}

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

(Spring Boot 2.1.3)