Spring BootのNo qualifying bean of typeエラーとDIできない場合の確認順を解説する記事のアイキャッチ。

Spring

Spring Bootの「No qualifying bean of type」原因|DIできないときの対処法

AIによる要約

Spring BootのNo qualifying bean of typeは、コンストラクタや@Autowiredで要求された型に合うBeanをSpringコンテナから見つけられないときに発生します。最初にエラーメッセージから「必要とされた型」と「要求したクラス」を特定し、その型の実装クラス、@Service・@Component・@Repository・@Bean、コンポーネントスキャン範囲を確認します。実装が複数ある場合は別のエラーであり、@Qualifierや@Primaryの検討対象です。required=falseやnullで起動だけ通すのではなく、その依存関係が必須か、どの実装を使う設計かを明確にします。

新人SE
新人SE
Serviceへコンストラクタを追加したら、アプリが起動しなくなりました。No qualifying bean of typeと出ています。
ポンコツSE
ポンコツSE
注入される側より先に、Springが探している型と、その型をBeanとして登録する定義を確認します。

No qualifying bean of typeは、Spring BootでDIを使い始めた新人が遭遇しやすい起動エラーです。ログにはUnsatisfiedDependencyExceptionNoSuchBeanDefinitionExceptionも一緒に表示されることがあります。

原因は「Autowiredが壊れた」ことではありません。Springへ依頼した型に対して、候補となるBeanが0件だから依存関係を組み立てられない、という意味です。エラー文から型を特定し、Beanが登録される条件を順番に確認すれば切り分けられます。

この記事のポイント

  • エラー文から要求された型と要求元クラスを読む
  • 実装クラスが存在するだけではBeanにならない
  • @Service・@Component・@Repository・@Beanを確認する
  • 起動クラス配下のコンポーネントスキャン範囲を確認する
  • 候補0件と候補複数件を混同しない
  • テストのスライスが必要なBeanを読み込んでいるか確認する

No qualifying bean of typeエラーの意味

次のようなServiceがあるとします。

@Service
public class OrderService {

    private final PaymentGateway paymentGateway;

    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

SpringはOrderServiceを作るとき、コンストラクタ引数のPaymentGateway型を満たすBeanをコンテナから探します。候補がなければ、次のような趣旨のエラーになります。

Parameter 0 of constructor in com.example.order.OrderService
required a bean of type 'com.example.payment.PaymentGateway'
that could not be found.

ここで重要なのは、Parameter 0OrderServicePaymentGatewayです。ログの最上段だけを読まず、最後のCaused by付近まで確認します。

「どの型がないか」と「誰がその型を必要としているか」をエラー文から1行ずつ拾うと、調査対象を絞れます。

原因1:実装クラスにSpringのアノテーションがない

インターフェースと実装クラスを作っただけでは、SpringのBeanとして自動登録されません。

public interface PaymentGateway {
    PaymentResult charge(PaymentRequest request);
}

// NG: クラスは存在するがSpringのBeanではない
public class CreditCardPaymentGateway
        implements PaymentGateway {

    @Override
    public PaymentResult charge(PaymentRequest request) {
        return new PaymentResult(true);
    }
}

アプリケーション内のサービス実装として自動検出させるなら、役割に合うアノテーションを付けます。

@Component
public class CreditCardPaymentGateway
        implements PaymentGateway {

    @Override
    public PaymentResult charge(PaymentRequest request) {
        return new PaymentResult(true);
    }
}

業務処理なら@Service、DBアクセスなら@Repository、Web入口なら@Controller、その他の構成要素なら@Componentが基本です。動かすためだけに全部へ@Componentを付けるのではなく、クラスの責務に合わせます。

原因2:外部ライブラリのクラスを@Bean登録していない

自分で編集できない外部ライブラリのクラスへ@Componentを付けることはできません。その場合は設定クラスで生成方法をSpringへ教えます。

@Configuration
public class PaymentConfig {

    @Bean
    public PaymentClient paymentClient(
            PaymentProperties properties) {
        return new PaymentClient(
                properties.getBaseUrl(),
                properties.getApiKey());
    }
}

@Beanメソッドの戻り値型も確認します。注入先が具象型を要求しているのに、Bean定義の公開型が別のインターフェースになっていると、型の探し方によって候補にならないことがあります。基本は注入先をインターフェースへ寄せ、必要以上に具象型へ依存しません。

原因3:コンポーネントスキャン範囲から外れている

アノテーションが付いていても、Springがそのクラスを探索しなければBean登録されません。Spring Bootでは、通常は起動クラスのパッケージと、その配下が探索対象です。

com.example.sales
├── SalesApplication.java
├── order
│   └── OrderService.java          // 対象
└── payment
    └── PaymentGatewayImpl.java   // 対象

com.example.shared
└── SharedPaymentGateway.java     // 構成によっては対象外

新規クラスだけ別の上位パッケージへ置いてエラーになった場合、既存のディレクトリ構成と起動クラスの位置を確認します。@ComponentScanを追加すれば動くことはありますが、複数モジュールやテストへの影響があるため、既存設計に沿った配置を優先します。

原因4:インターフェースだけ作って実装がない

設計を先に進めるためにインターフェースだけ追加し、実装クラスを作り忘れることがあります。インターフェース自体へ@Componentを付けても、通常は生成する実体がありません。

public interface StockRepository {
    Optional<Stock> findByProductId(Long productId);
}

Spring Data JPAのRepositoryインターフェースは、規約に従えばSpringが実装を生成します。一方、自作インターフェースは自動的に実装されません。「インターフェースならDIできる」ではなく、その実体を誰が生成するかを説明できる必要があります。

候補0件と候補複数件は別の問題

No qualifying bean of typeという言葉だけを見ると、候補がない場合と、候補を1つに決められない場合を混同しがちです。ログの続きまで読みます。

状態代表的なメッセージ確認内容
候補0件could not be foundアノテーション、@Bean、スキャン、実装
候補複数件expected single matching bean but found 2@Qualifier、@Primary、責務分離

実装が複数ある場合は、どの実装を使うかが仕様です。片方を削除したり、適当に@Primaryを付けたりせず、呼び出し側が何を選ぶべきかを確認します。

コンストラクタインジェクションで依存関係を明確にする

フィールドへ直接@Autowiredするより、コンストラクタで必須依存を表すと、何が不足しているか読みやすくなります。コンストラクタが1つだけなら、現在の一般的なSpring構成では@Autowiredを省略できます。

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;

    public OrderService(
            OrderRepository orderRepository,
            PaymentGateway paymentGateway) {
        this.orderRepository = orderRepository;
        this.paymentGateway = paymentGateway;
    }
}

Lombokの@RequiredArgsConstructorを使う現場もありますが、生成されるコンストラクタとfinalフィールドの関係を理解してから使います。エラー調査時は、ソース上に見えないコンストラクタも意識してください。

テストだけでエラーになる場合の確認

アプリは起動するのにテストだけ失敗する場合、テストが読み込む範囲を確認します。たとえば@WebMvcTestはController周辺へ絞ったテストであり、すべてのServiceを自動で読み込むわけではありません。

@WebMvcTest(OrderController.class)
class OrderControllerTest {

    @Autowired
    private MockMvc mockMvc;

    // プロジェクトのSpring Bootバージョンに合う
    // モックBean用アノテーションを使う
    @MockBean
    private OrderService orderService;
}

本番用クラスへテストの都合で@Componentを追加するのは避けます。テストが何を対象にするかを決め、必要な依存をモックまたはテスト設定として用意します。利用可能なモックBean用アノテーションは、プロジェクトで使っているSpring Boot・Spring Frameworkのバージョンに合わせてください。

required=falseで起動だけ通さない

エラーを消すために@Autowired(required = false)へ変更すると、依存オブジェクトがnullのまま処理へ進み、後からNullPointerExceptionになる可能性があります。

// 原則NG: 必須なのに任意依存へ変えてしまう
@Autowired(required = false)
private PaymentGateway paymentGateway;

本当に任意機能なら、未設定時の振る舞いまで設計します。必須機能なら、正しいBean定義を追加するのが修正です。

注意

newでServiceを直接生成してDIエラーを回避すると、そのServiceが必要とする別の依存やAOP、トランザクションが適用されません。Spring管理のクラスは、既存の生成方法へ合わせてください。

現場で使える確認順

  1. ログの最後のCaused byまで読む
  2. 要求された型と要求元クラスをメモする
  3. その型の実装クラスが存在するか確認する
  4. @Service・@Component・@Repository・@Beanを確認する
  5. コンポーネントスキャン範囲を確認する
  6. ProfileやConditionalで無効になっていないか確認する
  7. 候補0件か候補複数件か分ける
  8. テスト限定ならテストが読み込むBean範囲を確認する

コードレビューで伝えるコメント例

OrderServicePaymentGatewayを必須依存として受け取っていますが、現在の実装クラスにはBean登録の定義がありません。既存の構成に合わせてCreditCardPaymentGateway@Componentで登録する想定でしょうか。外部クライアントならConfigクラスの@Beanへ寄せる方針も確認したいです。

単に「@Serviceを付けてください」と書くより、必要な型、現在足りない登録、候補となる設計を示すと、修正理由が伝わります。

提出前のセルフチェック

レビュー前に確認すること

  • 要求された型をログから特定したか
  • 要求元のコンストラクタ引数を確認したか
  • 実装クラスが存在するか
  • 役割に合うBean登録アノテーションがあるか
  • @Beanメソッドが読み込まれるか
  • 起動クラス配下のスキャン範囲か
  • ProfileやConditionalの条件を満たすか
  • 候補0件と複数件を区別したか
  • required=falseで隠していないか
  • テストの読み込み範囲に合っているか

SpringのDIを体系的に確認する参考書

DIエラーはアノテーションの暗記だけでは解決しにくく、SpringコンテナがBeanを登録し、型を使って依存関係を解決する流れを理解すると切り分けやすくなります。

書籍「作って学ぶ Spring Boot入門」の表紙
商品画像:Amazon.co.jp
PR Java新人・若手向け

作って学ぶ Spring Boot入門

多田 真敏 (著)

Spring Bootを、動くWebアプリを作りながら覚える。

Controller、画面、DB連携などを一つのアプリとして実装できます。Java基礎からSpringの現場へ進む橋渡しに向く一冊です。

  • Spring Bootを手を動かして学びたい
  • Webアプリ開発の流れをつかみたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

Spring BootのNo qualifying bean of typeは、要求された型に合うBeanをSpringが見つけられないときに発生します。ログから要求型と要求元を特定し、実装、アノテーション、@Bean、スキャン範囲の順で確認します。

候補が0件なのか、複数あって選べないのかも分けてください。起動だけ通すために任意依存やnewへ逃げず、その依存が必須か、どの実装を使うかを設計として決めることが、現場で再発しない修正につながります。

Autowiredによる型解決の基本はSpring Framework公式ドキュメントでも確認できます。

-Spring
-, , , ,