Dev Study
← 解説「ApplicationContext と Bean 定義」に戻る

サンプルコードで身につける: ApplicationContext と Bean 定義

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1自分のクラスが、どんな名前の Bean になったか

に自分の を並べさせた出力です。 を付けたも countApp という になっています。名前を付けなければ名の先頭を小文字にしたものになり、("renamed") と書いた URLParser は renamed になりました。

CountApp の出力(Spring Boot 4.1.1、ログ行とバナーを除く)コマンド出力
context class = org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext
beanDefinitionCount = 148
mine: countApp -> l11.count.CountApp
mine: greetingService -> l11.count.GreetingService
mine: HTMLRenderer -> l11.count.HTMLRenderer
mine: renamed -> l11.count.URLParser
my beans = 4

2Bean 名の規則を素の Java で確かめる

上の出力で HTMLRenderer の先頭が小文字になっていないのは、先頭の2文字がどちらも大文字ならそのままにする規則があるためです。 のリファレンスは、これが の java.beans.Introspector.decapitalize と同じ規則だと書いています。そのを直接呼ぶと、同じ結果になります。

Java

3起点の兄弟パッケージは拾われない

メインクラスは l11.scan.app にあり、同じの InsideService と、兄弟パッケージ l11.scan.other の OutsideService の両方に @Component が付いています。起動はエラーなく終わりますが、OutsideService は Bean として存在しません。拾われなかったことは、尋ねて初めて分かります。

ScanApp の出力(Spring Boot 4.1.1)コマンド出力
insideService : true
outsideService : false
// l11/scan/app/ScanApp.java
package l11.scan.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication // 起点は l11.scan.app
public class ScanApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(ScanApp.class, args);
        System.out.println("insideService  : " + ctx.containsBean("insideService"));
        System.out.println("outsideService : " + ctx.containsBean("outsideService"));
        ctx.close();
    }
}

// l11/scan/app/InsideService.java   … package l11.scan.app;   @Component public class InsideService {}
// l11/scan/other/OutsideService.java … package l11.scan.other; @Component public class OutsideService {}

4うまくいかない場合: スキャン外のクラスを注入しようとした

起点の外にある OutsideService を、起点の中の OrderService がで要求した場合の起動ログです(終了コード 1、途中の行は省略)。Description の行が、どのコンストラクタの何番目の引数で、どの型の Bean が見つからなかったかを示しています。クラスは存在し @Component も付いているので、ソースだけでは原因に気づきにくい失敗です。

起動失敗のログ(Spring Boot 4.1.1、抜粋)追跡ログ
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
...
***************************
APPLICATION FAILED TO START
***************************
 
Description:
 
Parameter 0 of constructor in l11.scanfail.app.OrderService required a bean of type 'l11.scanfail.other.OutsideService' that could not be found.
 
 
Action:
 
Consider defining a bean of type 'l11.scanfail.other.OutsideService' in your configuration.

5同じ型の Bean が2つあるとき、型だけでは取り出せない

Notifier というの実装を2つ、どちらも @Component にした場合です。名前を指定すれば取り出せますが、型だけを渡すと、候補が2つあって1つに決められないというになり、メッセージの末尾に候補の Bean 名が並びます。コンストラクタでの注入で同じ状況にすると、起動そのものが失敗しました。

DupApp の出力(Spring Boot 4.1.1)コマンド出力
by name: MailNotifier
org.springframework.beans.factory.NoUniqueBeanDefinitionException
No qualifying bean of type 'l11.dup.Notifier' available: expected single matching bean but found 2: mailNotifier,slackNotifier
// l11/dup/DupApp.java(import は省略)
package l11.dup;

@SpringBootApplication
public class DupApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(DupApp.class, args);
        // 名前で取り出す
        System.out.println("by name: " + ctx.getBean("mailNotifier").getClass().getSimpleName());
        try {
            ctx.getBean(Notifier.class); // 型で取り出す
        } catch (NoUniqueBeanDefinitionException e) {
            System.out.println(e.getClass().getName());
            System.out.println(e.getMessage());
        }
        ctx.close();
    }
}

interface Notifier {}

@Component
class MailNotifier implements Notifier {}

@Component
class SlackNotifier implements Notifier {}

図やコードに出てくる用語(タップで説明)