L'exception BeanDefinitionOverrideException dans Spring Boot

1. Introduction

La mise à niveau de Spring Boot 2.1 a surpris plusieurs personnes avec des occurrences inattendues de BeanDefinitionOverrideException . Cela peut dérouter certains développeurs et les amener à s'interroger sur ce qui est arrivé au comportement de substitution du bean au printemps.

Dans ce didacticiel, nous allons résoudre ce problème et voir comment y remédier au mieux.

2. Dépendances de Maven

Pour notre exemple de projet Maven, nous devons ajouter la dépendance Spring Boot Starter:

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

3. Remplacement des haricots

Les beans Spring sont identifiés par leurs noms dans un ApplicationContext .

Ainsi, le remplacement de bean est un comportement par défaut qui se produit lorsque nous définissons un bean dans un ApplicationContext qui porte le même nom qu'un autre bean . Il fonctionne en remplaçant simplement l'ancien bean en cas de conflit de nom.

À partir de Spring 5.1, l' exception BeanDefinitionOverrideException a été introduite pour permettre aux développeurs de lever automatiquement l'exception afin d'éviter tout remplacement de bean inattendu . Par défaut, le comportement d'origine est toujours disponible, ce qui permet le remplacement de bean.

4. Changement de configuration pour Spring Boot 2.1

Spring Boot 2.1 a désactivé le remplacement du bean par défaut comme approche défensive. Le but principal est de remarquer les noms de haricots en double à l'avance pour éviter de surcharger les beans accidentellement .

Par conséquent, si notre application Spring Boot repose sur le remplacement de bean, il est très probable qu'il rencontre l' exception BeanDefinitionOverrideException après la mise à niveau de la version Spring Boot vers 2.1 et versions ultérieures.

Dans les sections suivantes, nous examinerons un exemple où l' exception BeanDefinitionOverrideException se produirait, puis nous discuterons de certaines solutions.

5. Identifier les haricots en conflit

Créons deux configurations Spring différentes, chacune avec une méthode testBean () , pour produire l' exception 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(); } } 

Ensuite, nous allons créer notre classe de test 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); } } 

L'exécution du test produit une BeanDefinitionOverrideException . Cependant, l'exception nous fournit des informations utiles:

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. 

Notez que l'exception révèle deux informations importantes.

Le premier est le nom du bean en conflit, testBean :

Invalid bean definition with name 'testBean' ... 

Et la seconde nous montre le chemin complet des configurations concernées:

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

En conséquence, nous pouvons voir que deux beans différents sont identifiés comme testBean provoquant un conflit. En outre, les beans sont contenus dans les classes de configuration TestConfiguration1 et TestConfiguration2 .

6. Solutions possibles

En fonction de notre configuration, Spring Beans a des noms par défaut sauf si nous les définissons explicitement.

Par conséquent, la première solution possible est de renommer nos beans.

Il existe des méthodes courantes pour définir des noms de haricots dans Spring.

6.1. Modification des noms de méthode

Par défaut, Spring prend le nom des méthodes annotées comme noms de bean .

Par conséquent, si nous avons des beans définis dans une classe de configuration, comme notre exemple, le simple fait de changer les noms de méthode empêchera l' exception BeanDefinitionOverrideException :

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

6.2. Annotation @Bean

L' annotation @Bean de Spring est une manière très courante de définir un bean.

Ainsi, une autre option est de définir le nom propriété de @Bean annotation:

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

6.3. Annotations stéréotypées

Une autre façon de définir un bean consiste à utiliser des annotations de stéréotypes. Avec la fonctionnalité @ComponentScan de Spring activée, nous pouvons définir nos noms de bean au niveau de la classe à l'aide de l' annotation @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.

Pour finir, nous devons être conscients qu'il est difficile de deviner quel bean aura la priorité car l'ordre de création du bean est déterminé par les relations de dépendance principalement influencées à l'exécution . Par conséquent, autoriser le remplacement des beans peut produire un comportement inattendu à moins que nous ne connaissions suffisamment bien la hiérarchie des dépendances de nos beans.

7. Conclusion

Dans ce didacticiel, nous avons expliqué ce que BeanDefinitionOverrideException signifie dans Spring, pourquoi il apparaît soudainement et comment y remédier après la mise à niveau de Spring Boot 2.1.

Comme toujours, le code source complet de cet article est disponible sur GitHub.