Spring Boot 2.1 переопределение бина против основного

В Spring Boot 2.1 переопределение bean-компонентов отключено по умолчанию, и это хорошо.

Однако у меня есть несколько тестов, где я заменяю bean-компоненты на mockito. При настройке по умолчанию тесты с такой конфигурацией не будут выполнены из-за переопределения бина.

Единственный способ, который я нашел работающим, состоял в том, чтобы включить переопределение bean-компонентов через свойства приложения:

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

Однако я действительно хотел бы обеспечить минимальную настройку определения bean-компонента для моей тестовой конфигурации, которая будет указана spring с отключенной переопределением.

Бобы, которые я переопределяю, либо

  • Определено в другой конфигурации, которая импортирована в мою тестовую конфигурацию
  • Автоматически обнаруженный бин путем сканирования аннотаций

То, о чем я думал, должно работать в тестовой конфигурации, переопределяя bean-компонент и @Primary к нему, как мы привыкли для конфигураций источника данных. Это, однако, не имеет никакого эффекта и @Primary меня задуматься: противоречиво ли переопределение бина @Primary и отключенного бина?

Пример:

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

Ответ 1

Переопределение 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 основной бин будет добавлен по умолчанию в:

@Autowired
BService bService;

Ответ 2

По умолчанию разрешено переопределять @Component с помощью @Bean. В твоем случае

@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

spring.main.allow-bean-definition-overriding=true может быть помещен в тестовые конфигурации. Если вам нужно обширное интеграционное тестирование, вам придется переопределить bean-компоненты в какой-то момент. Это неизбежно.

Я просто хотел еще раз подчеркнуть, что, хотя правильный ответ уже предоставлен, это означает, что у вашего компонента будут разные имена, так что технически это не переопределение. Реальное переопределение, если оно вам нужно, потому что вы используете @Qualifiers, @Resources или что-то подобное, начиная с Spring Boot 2.X, возможно только с spring.main.allow-bean-definition-overriding=true

Обновление: будьте осторожны с DSL определения Kotlin Bean. В Spring Boot для этого потребуется специальный ApplicationContextInitializer, например:

class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {

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

}

Теперь, если вы решите переопределить один из таких компонентов на основе DSL в своем тесте с помощью метода @Primary @Bean, это не сработает. Инициализатор включится после методов @Bean и вы все равно получите начальный bean-компонент на основе DSL в своих тестах даже с @Primary в тесте @Bean. Еще один вариант - создать инициализатор теста для ваших тестов и перечислить их все в свойствах теста, например, так (порядок имеет значение):

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

Определение Bean DSL также поддерживает первичное свойство через:

bean(isPrimary=true) {...}

- что вам понадобится, чтобы устранить неоднозначность, когда вы попытаетесь внедрить бин, однако main:allow-bean-definition-overriding: true не нужен, если вы идете чистым способом DSL.

(Spring Boot 2.1.3)