春の循環依存

1.循環依存とは何ですか?

Bean Aが別のBean Bに依存し、Bean BがBean Aにも依存している場合に発生します。

豆A→豆B→豆A

もちろん、もっと多くのBeanを暗示することができます

豆A→豆B→豆C→豆D→豆E→豆A

2春に起こること

SpringコンテキストがすべてのBeanをロードしているとき、それはそれらが完全に機能するのに必要な順序でBeanを作成しようとします。たとえば、次のように循環的な依存関係がなかったとします。

豆A→豆B→豆C

SpringはBean Cを作成し、次にBean Bを作成し(そしてその中にBean Cをインジェクトし)、次にBean Aを作成します(そしてそれにBean Bをインジェクトします)。

ただし、循環依存関係がある場合、SpringはどちらのBeanを最初に作成するかを決定できません。これらのBeanは互いに依存しているためです。このような場合、Springはコンテキストのロード中に BeanCurrentlyInCreationException を送出します。

Springでは コンストラクタインジェクション を使用すると発生する可能性があります。他の種類のインジェクションを使用する場合は、依存関係は必要なときにインジェクトされ、コンテキストのロード時にはインジェクトされないため、この問題は発生しません。

3簡単な例

相互に依存する2つのBeanを(コンストラクター注入を介して)定義しましょう。

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

これでテスト用のConfigurationクラスを書くことができます。それを TestConfig と呼びましょう。これはコンポーネントをスキャンするための基本パッケージを指定します。

私たちのBeanが“ com.baeldung.circulardependency ”パッケージで定義されているとしましょう。

@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}

そして最後に、循環依存関係をチェックするためのJUnitテストを書くことができます。

循環依存はコンテキストのロード中に検出されるため、テストは空になることがあります。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {

    @Test
    public void givenCircularDependency__whenConstructorInjection__thenItFails() {
       //Empty test; we just want the context to load
    }
}

このテストを実行しようとすると、次の例外が発生します。

BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
Requested bean is currently in creation: Is there an unresolvable circular reference?

4.回避策

この問題に対処するための最も一般的な方法をいくつか紹介します。

4.1. 再設計

あなたが循環的な依存関係を持っているとき、それはあなたがデザインの問題を抱えている可能性があり、そして責任はうまく分離されていません。コンポーネントの階層が適切に設計され、循環的な依存関係が不要になるように、コンポーネントを正しく設計し直す必要があります。

コンポーネントを再設計できない場合(その理由としては、レガシーコード、テスト済みで変更できないコード、完全な再設計に十分な時間やリソースがないなど)が考えられます。 。

4.2. @ Lazy を使用

このサイクルを破る簡単な方法は、SpringにBeanの1つを遅延初期化するように言うことです。つまり、Beanを完全に初期化するのではなく、他のBeanにインジェクトするためのプロキシを作成します。注入されたBeanは、最初に必要になったときにのみ完全に作成されます。

コードでこれを試すには、循環依存関係を次のように変更します。

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

今テストを実行すると、今回はエラーが発生しないことがわかります。

4.3. セッター/フィールドインジェクションを使用する

最も人気のある回避策の1つ、そしてhttp://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html[Spring documentation]が提案していることはセッターインジェクションの使用です。

コンストラクタインジェクションの代わりにセッターインジェクション(またはフィールドインジェクション)を使用するようにBeanの配線方法を変更した場合は、簡単に言えば問題ありません。この方法でSpringはBeanを作成しますが、依存関係は必要になるまで注入されません。

それでは、クラスをセッターインジェクションを使用するように変更し、 CircularDependencyB に別のフィールド( message )を追加して、適切な単体テストを実行しましょう。

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }

    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    private String message = "Hi!";

    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }

    public String getMessage() {
        return message;
    }
}

それでは、単体テストにいくつか変更を加える必要があります。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {

    @Autowired
    ApplicationContext context;

    @Bean
    public CircularDependencyA getCircularDependencyA() {
        return new CircularDependencyA();
    }

    @Bean
    public CircularDependencyB getCircularDependencyB() {
        return new CircularDependencyB();
    }

    @Test
    public void givenCircularDependency__whenSetterInjection__thenItWorks() {
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);

        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}

以下は、上記の注釈について説明しています。

@ Bean :これらのメソッドは注入するBeanの実装を取得するために使用する必要があることをSpringフレームワークに伝えます。

@ Test :テストはコンテキストからCircularDependencyA Beanを取得し、そのCircularDependencyBが正しく挿入されたことを表明し、その message プロパティの値を確認します。

4.4. @ PostConstruct を使う

サイクルを中断するもう1つの方法は、Beanの1つに @ Autowired を使用して依存関係を注入し、 @ PostConstruct というアノテーションを付けたメソッドを使用して他の依存関係を設定することです。

私たちの豆は以下のコードを持つことができます:

@Component
public class CircularDependencyA {

    @Autowired
    private CircularDependencyB circB;

    @PostConstruct
    public void init() {
        circB.setCircA(this);
    }

    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    private String message = "Hi!";

    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }

    public String getMessage() {
        return message;
    }
}

そして、以前と同じテストを実行できるので、循環依存関係の例外がまだスローされていないこと、および依存関係が正しく注入されていることを確認します。

4.5. ApplicationContextAware InitializingBean を実装します.

いずれかのBeanが ApplicationContextAware を実装している場合、そのBeanはSpringコンテキストにアクセスでき、そこから他のBeanを抽出できます。

InitializingBean を実装すると、このBeanはすべてのプロパティが設定された後に何らかのアクションを実行する必要があることがわかります。この場合は、依存関係を手動で設定します。

私たちの豆のコードは次のようになります。

@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {

    private CircularDependencyB circB;

    private ApplicationContext context;

    public CircularDependencyB getCircB() {
        return circB;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(CircularDependencyB.class);
    }

    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    private String message = "Hi!";

    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }

    public String getMessage() {
        return message;
    }
}

繰り返しますが、前のテストを実行して、例外がスローされず、テストが期待どおりに機能していることを確認できます。

5結論として

Springで循環依存関係に対処する方法はたくさんあります。考慮すべき最初の事柄は循環依存関係の必要性がないようにあなたの豆を再設計することです:それらは通常改善することができる設計の徴候です。

しかし、プロジェクトに循環的な依存関係が絶対に必要な場合は、ここで提案するいくつかの回避策に従うことができます。

好ましい方法はセッター注射を使用することである。しかし、SpringがBeanの初期化と注入を管理するのを止めて、何らかの戦略を使って自分自身でそれを行うことに基づいて、他の代替手段があります。

こちら に示されているBeanと、ユニットテストhttps://github.com/eugenp/tutorials/tree/master/spring-mvc-java/src/test/java/com/baeldung/circulardependency[こちら]。