1. 序章

Spring Boot 2.1のアップグレードは、BeanDefinitionOverrideExceptionの予期しない発生で何人かの人々を驚かせました。 一部の開発者を混乱させ、SpringでのBeanのオーバーライド動作に何が起こったのか疑問に思う可能性があります。

このチュートリアルでは、この問題を解明し、それに対処するための最善の方法を確認します。

2. Mavenの依存関係

この例のMavenプロジェクトでは、 Spring BootStarter依存関係を追加する必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

3. Beanのオーバーライド

Spring Beanは、ApplicationContext内の名前で識別されます。

したがって、 Beanのオーバーライドは、別のBeanと同じ名前のApplicationContext内でBeanを定義するときに発生するデフォルトの動作です。 名前が競合する場合は、以前のBeanを置き換えるだけで機能します。

Spring 5.1以降、BeanDefinitionOverrideExceptionが導入され、開発者が自動的に例外をスローして、予期しないBeanのオーバーライドを防止できるようになりました。 デフォルトでは、元の動作が引き続き利用可能であり、Beanのオーバーライドが可能です。

4. SpringBoot2.1の構成変更

Spring Boot 2.1は、防御的なアプローチとして、デフォルトでBeanのオーバーライドを無効にしました。 主な目的は、誤ってBeanをオーバーライドしないように、重複するBean名を事前に通知することです

したがって、Spring BootアプリケーションがBeanのオーバーライドに依存している場合、Spring Bootバージョンを2.1以降にアップグレードした後、BeanDefinitionOverrideExceptionが発生する可能性が非常に高くなります。

次のセクションでは、 BeanDefinitionOverrideException が発生する例を見てから、いくつかの解決策について説明します。

5. 競合するBeanの識別

testBean()メソッドを使用して2つの異なるSpring構成を作成し、 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();
    }
}

次に、SpringBootテストクラスを作成します。

@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.

例外により、2つの重要な情報が明らかになることに注意してください。

1つ目は、競合するBean名testBeanです。

Invalid bean definition with name 'testBean' ...

2つ目は、影響を受ける構成のフルパスを示しています。

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

その結果、2つの異なるBeanが testBean として識別され、競合が発生していることがわかります。 さらに、Beanは構成クラスTestConfiguration1およびTestConfiguration2内に含まれています。

6. 可能な解決策

構成によっては、明示的に設定しない限り、SpringBeanにはデフォルトの名前があります。

したがって、最初に考えられる解決策は、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. @Beanアノテーション

Springの@Bean アノテーションは、beanを定義する非常に一般的な方法です。

したがって、別のオプションは、@Beanアノテーションのnameプロパティを設定することです。

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

6.3. ステレオタイプ注釈

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定義を変更できない場合は、Beanのオーバーライドを許可するようにSpringBootを構成することで回避できます。

beanオーバーライドを有効にするには、application.propertiesspring.main.allow- bean-definition-overridingプロパティをtrueに設定しましょう。 ] ファイル:

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

これを行うことで、Bean定義を変更せずにBeanをオーバーライドできるようにSpringBootに指示しています。

最後の通知として、 Beanの作成順序は、実行時に主に影響を受ける依存関係によって決定されるため、どのBeanが優先されるかを推測するのは難しいことに注意してください。 したがって、Beanのオーバーライドを許可すると、Beanの依存関係の階層を十分に理解していない限り、予期しない動作が発生する可能性があります。

7. 結論

このチュートリアルでは、Springでの BeanDefinitionOverrideException の意味、突然表示される理由、およびSpringBoot2.1のアップグレード後に対処する方法について説明しました。

いつものように、この記事の完全なソースコードはGitHubにあります。