BeanDefinitionOverrideException trong Spring Boot

1. Giới thiệu

Bản nâng cấp Spring Boot 2.1 đã khiến một số người ngạc nhiên với sự xuất hiện không mong muốn của BeanDefinitionOverrideException . Nó có thể gây nhầm lẫn cho một số nhà phát triển và khiến họ tự hỏi về điều gì đã xảy ra với hành vi ghi đè bean trong Spring.

Trong hướng dẫn này, chúng tôi sẽ làm sáng tỏ vấn đề này và xem cách tốt nhất để giải quyết nó.

2. Sự phụ thuộc của Maven

Đối với dự án Maven mẫu của chúng tôi, chúng tôi cần thêm phụ thuộc Spring Boot Starter:

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

3. Ghi đè đậu

Spring bean được xác định bằng tên của chúng trong ApplicationContext .

Do đó, ghi đè bean là một hành vi mặc định xảy ra khi chúng ta định nghĩa một bean trong ApplicationContext có cùng tên với một bean khác . Nó hoạt động bằng cách thay thế bean cũ trong trường hợp xung đột tên.

Bắt đầu từ Spring 5.1, BeanDefinitionOverrideException được giới thiệu để cho phép các nhà phát triển tự động ném ngoại lệ để ngăn chặn bất kỳ việc ghi đè bean không mong muốn nào . Theo mặc định, hành vi gốc vẫn có sẵn cho phép ghi đè bean.

4. Thay đổi cấu hình cho Spring Boot 2.1

Spring Boot 2.1 đã tắt tính năng ghi đè bean theo mặc định như một cách tiếp cận phòng thủ. Mục đích chính là để thông báo trước các tên đậu trùng lặp để tránh vô tình ghi đè đậu .

Do đó, nếu ứng dụng Spring Boot của chúng tôi dựa trên ghi đè bean, rất có thể gặp phải BeanDefinitionOverrideException sau khi chúng tôi nâng cấp phiên bản Spring Boot lên 2.1 trở lên.

Trong các phần tiếp theo, chúng ta sẽ xem xét một ví dụ nơi mà BeanDefinitionOverrideException sẽ xảy ra và sau đó chúng ta sẽ thảo luận về một số giải pháp.

5. Xác định Đậu trong Xung đột

Hãy tạo hai cấu hình Spring khác nhau, mỗi cấu hình có một phương thức testBean () , để tạo ra 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(); } } 

Tiếp theo, chúng ta sẽ tạo lớp kiểm tra 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); } } 

Chạy thử nghiệm tạo ra một BeanDefinitionOverrideException . Tuy nhiên, ngoại lệ cung cấp cho chúng tôi một số thông tin hữu ích:

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. 

Lưu ý rằng ngoại lệ tiết lộ hai phần thông tin quan trọng.

Đầu tiên là tên bean xung đột, testBean :

Invalid bean definition with name 'testBean' ... 

Và phần thứ hai cho chúng ta thấy đường dẫn đầy đủ của các cấu hình bị ảnh hưởng:

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

Kết quả là chúng ta có thể thấy rằng hai bean khác nhau được xác định là testBean gây ra xung đột. Ngoài ra, các bean được chứa bên trong các lớp cấu hình TestConfiguration1TestConfiguration2 .

6. Giải pháp khả thi

Tùy thuộc vào cấu hình của chúng tôi, Spring Beans có tên mặc định trừ khi chúng tôi đặt chúng một cách rõ ràng.

Do đó, giải pháp khả thi đầu tiên là đổi tên hạt đậu của chúng ta.

Có một số cách phổ biến để đặt tên bean trong Spring.

6.1. Thay đổi tên phương pháp

Theo mặc định, Spring lấy tên của các phương thức được chú thích làm tên bean .

Do đó, nếu chúng ta có các bean được định nghĩa trong một lớp cấu hình, như ví dụ của chúng ta, thì chỉ cần thay đổi tên phương thức sẽ ngăn chặn BeanDefinitionOverrideException :

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

6.2. @Bean Annotation

Chú thích @Bean của Spring là một cách rất phổ biến để xác định một bean.

Do đó, một tùy chọn khác là đặt thuộc tính tên của chú thích @Bean :

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

6.3. Chú thích khuôn mẫu

Một cách khác để xác định bean là với các chú thích khuôn mẫu. Với tính năng @ComponentScan của Spring được bật, chúng ta có thể xác định tên bean của mình ở cấp lớp bằng cách sử dụng chú thích @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.

Thông báo cuối cùng, chúng ta nên biết rằng rất khó đoán bean nào sẽ được ưu tiên vì thứ tự tạo bean được xác định bởi các mối quan hệ phụ thuộc chủ yếu ảnh hưởng trong thời gian chạy . Do đó, việc cho phép ghi đè đậu có thể tạo ra hành vi không mong muốn trừ khi chúng ta biết đủ rõ hệ thống phân cấp phụ thuộc của đậu.

7. Kết luận

Trong hướng dẫn này, chúng tôi đã giải thích BeanDefinitionOverrideException có nghĩa là gì trong Spring, tại sao nó đột nhiên xuất hiện và cách giải quyết nó sau khi nâng cấp Spring Boot 2.1.

Giống như mọi khi, mã nguồn hoàn chỉnh của bài viết này có thể được tìm thấy trên GitHub.