Dev Study
← Spring コース

45. Bean のライフサイクルとスコープ

はが作り、コンテナが片付けます。実測した順序は、、への注入、 を付けた、利用、を閉じたときの でした。コンストラクタの時点では、セッターで入る依存はまだ null です。@PostConstruct の時点では入っているので、依存がそろってから行いたい初期化はここに書けます。

は、1つの からをいくつ作るかの決まりです。既定は で、コンテナごと・定義ごとに1つです。その1つを、リクエストを受け持つ複数のが同時に通ります。NestJS も既定はシングルトンですが、公式ドキュメントは、Node.js はリクエストごとに別のスレッドで処理するモデルではないのでシングルトンは安全だ、と説明しています。同じ既定でも、その前提が Java 側にはありません。

コントローラのに int を置いて count++ するだけの処理に、20000 件を同時 50 で送ると、5回中5回とも 20000 に届きませんでした(10 CPU の環境)。隣に置いた は5回とも 20000 でした。原因は ではなく、同じオブジェクトのフィールドを複数のスレッドが同期なしで読み書きすることです。取りこぼす件数は回ごとに違い、何件失うとは言えません。

点線の付いた語はタップすると説明が出ます。用語集を開く →

singleton の Bean がたどる順序(Spring Boot 4.1.1 での実測をもとに作図)
コンストラクタコンストラクタの引数の依存は入っている
セッターで入る依存はまだ null
コンテナが続けて呼ぶ
セッターへの注入@Autowired を付けたセッター
依存がそろった
@PostConstruct初期化を書く場所
run が返る
利用1つのインスタンスを複数のスレッドが同時に通る
フィールドの状態は全リクエストで共有
コンテキストを閉じる
@PreDestroy片付けを書く場所
  • ・Spring Framework のリファレンスは singleton を既定のスコープとし、その範囲を per-container and per-bean(コンテナごと・Bean ごと)と表現しています
  • ・同リファレンスは、状態を持つ Bean には prototype、持たない Bean には singleton を使うことを原則として挙げています
  • ・リクエストとスレッドの関係は、後のレッスンで扱います

サンプルコード(フレームワーク環境が必要なため表示のみ)

// l12/life/LifeApp.java — コンテナが Bean に対して行うことを、順に出力する
package l12.life;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class LifeApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(LifeApp.class, args);
        System.out.println("5. run() returned; using the bean");
        ctx.getBean(ReportService.class).report();
        System.out.println("7. ctx.close()");
        ctx.close();
        System.out.println("9. closed");
    }
}

@Component
class Clock {
    Clock() { System.out.println("1. Clock constructor"); }
}

@Component
class ReportService {
    private final Clock clock;      // コンストラクタで受け取る
    private Formatter formatter;    // セッターで受け取る

    ReportService(Clock clock) {
        this.clock = clock;
        System.out.println("2. ReportService constructor (clock=" + (clock != null) + ", formatter=" + (formatter != null) + ")");
    }

    @Autowired
    void setFormatter(Formatter formatter) {
        this.formatter = formatter;
        System.out.println("3. ReportService setter injection (formatter=" + (formatter != null) + ")");
    }

    @PostConstruct // 依存が入った後に、コンテナが呼ぶ
    void init() { System.out.println("4. ReportService @PostConstruct (formatter=" + (formatter != null) + ")"); }

    void report() { System.out.println("6. ReportService.report() called"); }

    @PreDestroy // コンテキストを閉じるときに、コンテナが呼ぶ
    void shutdown() { System.out.println("8. ReportService @PreDestroy"); }
}

@Component
class Formatter {
    Formatter() { System.out.println("   Formatter constructor"); }
}

// Spring Boot 4.1.1 / JDK 25 での出力(ログ行とバナーを除く)
//
// 1. Clock constructor
//    Formatter constructor
// 2. ReportService constructor (clock=true, formatter=false)
// 3. ReportService setter injection (formatter=true)
// 4. ReportService @PostConstruct (formatter=true)
// 5. run() returned; using the bean
// 6. ReportService.report() called
// 7. ctx.close()
// 8. ReportService @PreDestroy
// 9. closed
//
// Clock・Formatter・ReportService の順に作られたのは今回の観測。
// 依存関係のない Bean どうしの生成順は、規則として当てにしない

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

サンプルコードで身につける(5本) →

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

公式ドキュメントで詳しく ↗