«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-компонентов.