Изключението BeanDefinitionOverrideExption в пролетното зареждане

1. Въведение

Надстройката на Spring Boot 2.1 изненада няколко души с неочаквани случаи на BeanDefinitionOverrideException . Това може да обърка някои разработчици и да ги накара да се замислят какво се е случило с поведението, заместващо боб през пролетта.

В този урок ще разгадаем този проблем и ще видим как най-добре да го решим.

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

За нашия примерен проект Maven трябва да добавим зависимостта Spring Boot Starter:

 org.springframework.boot spring-boot-starter 2.3.3.RELEASE 

3. Заместване на боб

Пролетните зърна се идентифицират по имената им в ApplicationContext .

По този начин заменянето на боб е поведение по подразбиране, което се случва, когато дефинираме боб в ApplicationContext, който има същото име като друг боб . Той работи, като просто замени предишния боб в случай на конфликт на име.

Започвайки през пролетта 5.1, BeanDefinitionOverrideException е въведен, за да позволи на разработчиците автоматично да хвърлят изключението, за да предотвратят неочаквано заменяне на боб . По подразбиране все още е налично оригиналното поведение, което позволява заменяне на боб.

4. Промяна на конфигурацията за Spring Boot 2.1

Spring Boot 2.1 деактивира заменянето на боб по подразбиране като защитен подход. Основната цел е предварително да се забележат дублираните имена на зърна, за да се предотврати случайно заместване на зърната .

Следователно, ако нашето приложение 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.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. 

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

Първият е конфликтното име на боб, testBean :

Invalid bean definition with name 'testBean' ... 

И втората ни показва пълния път на засегнатите конфигурации:

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

В резултат на това можем да видим, че два различни зърна са идентифицирани като testBean, причиняващ конфликт. Освен това компонентите се съдържат в конфигурационните класове TestConfiguration1 и TestConfiguration2 .

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

В зависимост от нашата конфигурация Spring Beans имат имена по подразбиране, освен ако не ги зададем изрично.

Следователно първото възможно решение е да преименуваме нашите зърна.

Има няколко често срещани начина за задаване на имена на зърна през пролетта.

6.1. Промяна на имената на методите

По подразбиране Spring приема името на коментираните методи като имена на боб .

Следователно, ако имаме боб, дефиниран в конфигурационен клас, като нашия пример, тогава простото промяна на имената на методите ще попречи на BeanDefinitionOverrideException :

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

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

Анотацията на Spring @ @ Bean е много често срещан начин за определяне на боб.

По този начин, друга опция е да зададете свойството на името на @Bean анотация:

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

6.3. Анотации на стереотипите

Друг начин за дефиниране на боб е със стереотипни анотации. С активираната функция @ComponentScan на Spring можем да дефинираме имената на нашите компоненти на ниво клас, като използваме анотацията @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. Beans Coming From 3rd Party Libraries

In some cases, it's possible to encounter a name conflict caused by beans originating from 3rd party spring-supported libraries.

When this happens, we should attempt to identify which conflicting bean belongs to our application, to determine if any of the above solutions can be used.

However, if we are unable to alter any of the bean definitions, then configuring Spring Boot to allow bean overriding can be a workaround.

To enable bean overriding, let's set the spring.main.allow-bean-definition-overriding property to true in our application.properties file:

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

By doing this, we are telling Spring Boot to allow bean overriding without any change to bean definitions.

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

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

В този урок обяснихме какво означава BeanDefinitionOverrideException през пролетта, защо внезапно се появява и как да го адресираме след надстройката на Spring Boot 2.1.

Както винаги, пълният изходен код на тази статия може да бъде намерен в GitHub.