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>3.1.5</version>
</dependency>3. Bean 覆蓋
Spring Bean 通過其在 <em >ApplicationContext</em> 中的名稱進行標識。
因此,<strong >Bean 覆蓋</strong> 是一個默認行為,當我們在 <em >ApplicationContext</em> 中定義一個與另一個 Bean 具有相同名稱的 Bean 時發生。它通過簡單地替換原來的 Bean 來解決名稱衝突。
從 Spring 5.1 開始,`BeanDefinitionOverrideException 被引入,允許開發者自動拋出異常以防止意外的 Bean 覆蓋。 默認行為仍然可用,允許 Bean 覆蓋。
4. Spring Boot 2.1 配置更改
Spring Boot 2.1 默認禁用 Bean 覆蓋(Bean overriding),作為一種防禦措施。其主要目的是提前注意到重複的 Bean 名稱,以防止意外覆蓋 Bean。
因此,如果我們的 Spring Boot 應用程序依賴於 Bean 覆蓋,升級到 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();
}
}
接下來,我們將創建我們的 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.
請注意,異常揭示了兩個重要信息。
第一個是衝突的 Bean 名稱:testBean:
Invalid bean definition with name 'testBean' ...
第二項展示了所有受影響配置的完整路徑:
... com.baeldung.beandefinitionoverrideexception.TestConfiguration2 ...
... com.baeldung.beandefinitionoverrideexception.TestConfiguration1 ...
因此,我們可以看到兩個不同的 Bean 被識別為 testBean,從而導致衝突。此外,這些 Bean 包含在配置類 TestConfiguration1 和 TestConfiguration2 中。
6. 可能的解決方案
根據我們的配置,Spring Bean 默認具有名稱,除非我們明確設置它們。
因此,第一個可能的解決方案是重命名我們的 Bean。 有一些常見的在 Spring 中設置 Bean 名稱的方法。
6.1. 修改方法名
默認情況下,Spring 會將標註方法的名稱作為 Bean 名稱。
因此,如果我們在配置類中定義了 Bean,如我們的示例,只需更改方法名即可防止出現 BeanDefinitionOverrideException:
@Bean
public TestBean1 testBean1() {
return new TestBean1();
}
@Bean
public TestBean2 testBean2() {
return new TestBean2();
}
6.2. <em @Bean@ 註解
Spring 的 <em @Bean@ 註解是一種非常常見的定義 Bean 的方式。
因此,另一個選項是設置 <em @Bean@ 註解的 <em name@ 屬性:
@Bean("testBean1")
public TestBean1 testBean() {
return new TestBean1();
}
@Bean("testBean2")
public TestBean1 testBean() {
return new TestBean2();
}
6.3. 樣板標註 (Stereotype Annotations)
通過樣板標註,可以定義 Bean。當 Spring 啓用了 @ComponentScan 功能時,我們可以使用 @Component 註解在類級別定義 Bean 名稱:
@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 而導致名稱衝突。
當這種情況發生時,我們應該嘗試確定哪個 Bean 屬於我們的應用程序,以確定是否可以使用上述解決方案。
但是,如果無法修改任何 Bean 定義,則配置 Spring Boot 允許 Bean 覆蓋可以作為一種解決方法。
為了啓用 Bean 覆蓋,我們將設置 <em >spring.main.allow-bean-definition-overriding</em > 屬性為 <em >true</em >> 在我們的 <em >application.properties</em >> 文件中:
spring.main.allow-bean-definition-overriding=true
通過這樣做,我們告訴 Spring Boot 允許在不修改 Bean 定義的情況下進行 Bean 覆蓋。
最後,我們應該意識到 由於 Bean 創建順序由依賴關係決定,並且這些依賴關係主要在運行時確定,因此很難猜測哪個 Bean 將具有優先權。
7. 結論
在本文中,我們解釋了在 Spring 中 <em >BeanDefinitionOverrideException</em> 的含義、它突然出現的原理,以及在 Spring Boot 2.1 升級後如何解決它。