«1. Введение

Обновление Spring Boot 2.1 удивило нескольких людей неожиданным появлением исключения BeanDefinitionOverrideException. Это может сбить с толку некоторых разработчиков и заставить их задуматься о том, что случилось с переопределяющим поведением bean-компонента в Spring.

В этом уроке мы разберемся с этой проблемой и посмотрим, как лучше всего ее решить.

2. Зависимости Maven

Для нашего примера проекта Maven нам нужно добавить зависимость Spring Boot Starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

3. Переопределение компонентов

Компоненты Spring идентифицируются по их именам в ApplicationContext.

Таким образом, переопределение bean-компонента — это поведение по умолчанию, которое происходит, когда мы определяем bean-компонент в ApplicationContext, который имеет то же имя, что и другой bean-компонент. Он работает, просто заменяя прежний компонент в случае конфликта имен.

Начиная с Spring 5.1, было введено исключение BeanDefinitionOverrideException, позволяющее разработчикам автоматически создавать исключение для предотвращения любого неожиданного переопределения компонента. По умолчанию исходное поведение по-прежнему доступно, что позволяет переопределить bean-компонент.

4. Изменение конфигурации для Spring Boot 2.1

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

Поэтому, если наше приложение Spring Boot полагается на переопределение bean-компонентов, оно, скорее всего, столкнется с BeanDefinitionOverrideException после того, как мы обновим версию Spring Boot до версии 2.1 и выше.

В следующих разделах мы рассмотрим пример возникновения исключения BeanDefinitionOverrideException, а затем обсудим некоторые решения.

5. Идентификация Bean-компонентов в конфликте

Давайте создадим две разные конфигурации Spring, каждая с методом testBean(), для создания исключения BeanDefinitionOverrideException:

@Configuration
public class TestConfiguration1 {

    class TestBean1 {
        private String name;

        // standard getters and setters

    }

    @Bean
    public TestBean1 testBean(){
        return new TestBean1();
    }
}
@Configuration
public class TestConfiguration2 {

    class TestBean2 {
        private String name;

        // standard getters and setters

    }

    @Bean
    public TestBean2 testBean(){
        return new TestBean2();
    }
}

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TestConfiguration1.class, TestConfiguration2.class})
public class SpringBootBeanDefinitionOverrideExceptionIntegrationTest {

    @Test
    public void whenBeanOverridingAllowed_thenTestBean2OverridesTestBean1() {
        Object testBean = applicationContext.getBean("testBean");

        assertThat(testBean.getClass()).isEqualTo(TestConfiguration2.TestBean2.class);
    }
}

Далее мы создадим наш Spring Boot класс теста:

Invalid bean definition with name 'testBean' defined in ... 
... com.baeldung.beandefinitionoverrideexception.TestConfiguration2 ...
Cannot register bean definition [ ... defined in ... 
... com.baeldung.beandefinitionoverrideexception.TestConfiguration2] for bean 'testBean' ...
There is already [ ... defined in ...
... com.baeldung.beandefinitionoverrideexception.TestConfiguration1] bound.

Выполнение теста приводит к исключению BeanDefinitionOverrideException. Однако исключение предоставляет нам некоторую полезную информацию:

Обратите внимание, что исключение раскрывает две важные части информации.

Invalid bean definition with name 'testBean' ...

Первый — конфликтующее имя bean-компонента, testBean:

... com.baeldung.beandefinitionoverrideexception.TestConfiguration2 ...
... com.baeldung.beandefinitionoverrideexception.TestConfiguration1 ...

А второй показывает нам полный путь затронутых конфигураций:

В результате мы видим, что два разных bean-компонента идентифицируются как testBean, вызывающие конфликт. Кроме того, bean-компоненты содержатся внутри классов конфигурации TestConfiguration1 и TestConfiguration2.

6. Возможные решения

В зависимости от нашей конфигурации Spring Bean-компоненты имеют имена по умолчанию, если мы не укажем их явно.

Поэтому первое возможное решение — переименовать наши бины.

Есть несколько распространенных способов установки имен компонентов в Spring.

6.1. Изменение имен методов

По умолчанию Spring использует имена аннотированных методов в качестве имен компонентов.

@Bean
public TestBean1 testBean1() {
    return new TestBean1();
}
@Bean
public TestBean2 testBean2() {
    return new TestBean2();
}

Следовательно, если у нас есть bean-компоненты, определенные в классе конфигурации, как в нашем примере, то простое изменение имен методов предотвратит исключение BeanDefinitionOverrideException:

6.2. @Bean Annotation

@Bean("testBean1")
public TestBean1 testBean() {
    return new TestBean1();
}
@Bean("testBean2")
public TestBean1 testBean() {
    return new TestBean2();
}

Spring-аннотация @Bean — очень распространенный способ определения bean-компонента.

Таким образом, другой вариант — установить свойство name аннотации @Bean:

@Component("testBean1")
class TestBean1 {

    private String name;

    // standard getters and setters

}
@Component("testBean2")
class TestBean2 {

    private String name;

    // standard getters and setters

}

6.3. Стереотипные аннотации

Еще один способ определить bean-компонент — использовать стереотипные аннотации. С включенной функцией Spring @ComponentScan мы можем определить имена наших компонентов на уровне класса, используя аннотацию @Component:

6.4. Bean-компоненты из сторонних библиотек

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

В некоторых случаях можно столкнуться с конфликтом имен, вызванным bean-компонентами из сторонних библиотек, поддерживаемых Spring.

Когда это происходит, мы должны попытаться определить, какой конфликтующий bean-компонент принадлежит нашему приложению, чтобы определить, можно ли использовать какое-либо из приведенных выше решений.

Однако, если мы не можем изменить какое-либо из определений bean-компонентов, то настройка Spring Boot для разрешения переопределения bean-компонентов может быть обходным путем.

«Чтобы включить переопределение bean-компонента, давайте установим для свойства spring.main.allow-bean-definition-overriding значение true в нашем файле application.properties:

Делая это, мы сообщаем Spring Boot разрешить переопределение bean-компонента без перейти к определениям bean-компонентов.