Перейти к основному содержимому

Исключение BeanDefinitionOverrideException в Spring Boot

· 5 мин. чтения

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. Идентификация компонентов в конфликте

Давайте создадим две разные конфигурации 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();
}
}

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

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

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

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

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

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

Invalid bean definition with name 'testBean' ...

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

... com.foreach.beandefinitionoverrideexception.TestConfiguration2 ...
... com.foreach.beandefinitionoverrideexception.TestConfiguration1 ...

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

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

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

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

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

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

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

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

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

6.2. @Bean- аннотация

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

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

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

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

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

@Component("testBean1")
class TestBean1 {

private String name;

// standard getters and setters

}
@Component("testBean2")
class TestBean2 {

private String name;

// standard getters and setters

}

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

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

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

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

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

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

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

В качестве последнего замечания, мы должны знать, что трудно угадать, какой компонент будет иметь приоритет, потому что порядок создания компонента определяется отношениями зависимости, на которые в основном влияет время выполнения . Следовательно, разрешение переопределения компонента может привести к неожиданному поведению, если мы не знаем достаточно хорошо иерархию зависимостей наших компонентов.

7. Заключение

В этом руководстве мы объяснили, что означает BeanDefinitionOverrideException в Spring, почему оно внезапно появляется и как его устранить после обновления Spring Boot 2.1.

Как всегда, полный исходный код этой статьи можно найти на GitHub .