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 .