プログラミング

実装・エラー解決

Springで「NoUniqueBeanDefinitionException」が出たときの対策

この記事に書いてあること

同じインターフェースの実装が二つ登録されていると、型だけで依存先を指定した処理は、アプリケーションの起動時に失敗します。

実行用サンプル

Bean候補が二つのときの起動エラーと、実装Bを指定した場合の実行結果をJUnitで確かめます。 ZIPにはソース全文・テスト・pom.xml・READMEを収録しています。JDK 17とMaven 3.9系で mvn test を実行してください。

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'info.syachiku.field.SampleLogic' available: expected single matching bean but found 2: sampleLogicImpl,sampleLogicImpl2

例外に出る sampleLogicImpl と sampleLogicImpl2 が Bean の候補です。どちらを注入すべきか Spring が一つに決められていません。

これを解決します。この記事なんかで書いている方式で実装しようとするとぶつかることが多い課題だと思います。

前提

  • Spring Framework 4.xx
  • java8

元記事の環境は Spring Framework 4 系と Java 8 です。配布コードは Spring Framework 6.2.19 と JDK 17 で確認しました。

原因

以下のようなクラス構成を見てください。

インタフェースを使ったサンプルクラス図

次の AmbiguousController を、二つの SampleLogic 実装と一緒に登録すると、NoUniqueBeanDefinitionException が原因となって起動に失敗します。

package info.syachiku.field;

import org.springframework.beans.factory.annotation.Autowired;

public class AmbiguousController {

    @Autowired
    private SampleLogic sampleLogic;

    public String execute() {
        return sampleLogic.run();
    }
}

二つとも SampleLogic 型で、フィールドにも注入先の絞り込みがありません。クラス図の Controller に相当する失敗例を、サンプルでは AmbiguousController としています。

実装Bを使うという要件なら、候補の登録名と注入側の指定を揃えます。

対策

注入側に @Qualifier("sampleLogicImpl2") を指定します。ここでは実装Bの @Component に付けた Bean 名で候補を絞ります。

package info.syachiku.field;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class SampleController {

    @Autowired
    @Qualifier("sampleLogicImpl2")
    private SampleLogic sampleLogic;

    public String execute() {
        return sampleLogic.run();
    }
}

実装クラスは @Component の値で登録名を指定します。実装Aは sampleLogicImpl、実装Bは sampleLogicImpl2 です。

package info.syachiku.field;

import org.springframework.stereotype.Component;

@Component("sampleLogicImpl")
public class SampleLogicImpl implements SampleLogic {
    @Override
    public String run() {
        return "A";
    }
}
package info.syachiku.field;

import org.springframework.stereotype.Component;

@Component("sampleLogicImpl2")
public class SampleLogicImpl2 implements SampleLogic {
    @Override
    public String run() {
        return "B";
    }
}

Springは指定した登録名の実装Bを注入します。テストでは SampleController#execute() が B を返すことを確認します。

すべての実装を順に使う仕様なら List<SampleLogic> を注入する方法もあります。一つの実装を使う今回の仕様では @Qualifier を使います。

-実装・エラー解決
-,