Spring Bootの自動構成(AutoConfiguration)の仕組みを理解する


Spring Bootを使い始めると「なぜ何も設定していないのに動くんだろう?」と感じる場面がありますよね。依存を追加しただけでBeanが登録されて、ちゃんと動いてしまう。便利なのは確かですが、何か問題が起きたときにデバッグできないと困ります。

この記事では、AutoConfigurationの仕組みを起動シーケンスに沿って解説します。読み終えたら「なぜこのBeanが登録されたのか」をログで確認でき、自分でカスタムAutoConfigurationも書けるようになることを目指しましょう。

AutoConfigurationとは

AutoConfigurationは、クラスパスの状態やプロパティ値などの条件に応じて、自動的にBeanを登録する仕組み です。開発者が@Beanを一切書かなくても、依存を追加するだけで必要なBeanが揃ってしまうのはこのおかげです。

@SpringBootApplicationには@EnableAutoConfigurationが含まれており、これがAutoConfigurationの起点になります。

起動シーケンスの全体像

大まかな流れはこうです。

  1. @EnableAutoConfigurationがトリガーになる
  2. AutoConfigurationImportSelectorがAutoConfigurationクラスの候補一覧を読み込む
  3. 各クラスの@Conditionalアノテーションを評価する
  4. 条件を満たしたクラスのBeanだけが登録される

この流れを頭に入れておくと、以降の説明が格段に理解しやすくなります。

AutoConfigurationクラスの候補一覧はどこにある?

AutoConfigurationクラスの一覧は、JARファイル内のメタデータファイルに記述されています。

Spring Boot 2.x までMETA-INF/spring.factoriesに書かれていました。

# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.MyAutoConfiguration

Spring Boot 3.x からMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsというファイルに移行しました。

com.example.MyAutoConfiguration

既存プロジェクトで両ファイルが共存している場合も動作しますが、Spring Boot 3.xプロジェクトでは新形式に統一するのが推奨です。spring-boot-autoconfigure.jarを解凍して中身を見ると、数百のAutoConfigurationクラスが登録されているのがわかります。

AutoConfigurationImportSelectorの役割

@EnableAutoConfigurationは内部でAutoConfigurationImportSelectorを使っています。このクラスが候補一覧ファイルを読み込み、除外設定を適用した上でクラスを絞り込みます。

除外したいAutoConfigurationがある場合は次のように指定できます。

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class MyApplication { ... }

あるいはapplication.propertiesで指定することも可能です。

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

@ConditionalアノテーションによるBean登録の条件判定

AutoConfigurationクラスには@Conditional系のアノテーションが付いており、「どの条件を満たしたときにBeanを登録するか」を決めています。よく使われるものを紹介します。

アノテーション条件
@ConditionalOnClass指定クラスがクラスパスにある場合
@ConditionalOnMissingBean同型のBeanがまだ登録されていない場合
@ConditionalOnProperty指定プロパティが設定されている場合(例:spring.xxx.enabled=true
@ConditionalOnWebApplicationWebアプリケーションの場合

複数の@Conditionalが付いている場合は すべての条件を満たす必要があります 。ANDロジックで評価されるため、どれか一つでも条件を満たさなければBeanは登録されません。

@ConditionalOnPropertyはProfileと組み合わせて使うケースも多いです。環境ごとの設定切り替えについてはSpring BootのProfileを使って環境によって違う設定を安全に切り替える方法も参考にしてください。

DataSourceAutoConfigurationで読む実例

実際のコードで確認してみましょう。以下は概念を示すための簡略版です(実際のSpring Boot 3.xソースではEmbeddedDatabaseConfigurationPooledDataSourceConfigurationという内部@Configurationクラスに分割されていますが、仕組みの理解にはこれで十分です)。

@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        // DataSourceを生成して返す
    }
}

ポイントは2つです。

  • @ConditionalOnClass(DataSource.class) はJDBCドライバがクラスパスにある場合のみ有効にする
  • @ConditionalOnMissingBean はすでにDataSourceのBeanが登録されていれば登録しない

この2番目の条件のおかげで、独自のDataSourceを定義すればAutoConfigurationのDataSourceは無視されます。「自分で設定したものが優先される」のはこの仕組みによるものです。

debug=trueでConditions Evaluationレポートを読む

「Beanが登録されていない」「なぜか動かない」というときに役立つのがdebug=trueです。

# application.properties
debug=true

起動すると Conditions Evaluation Report がコンソールに出力されます。

============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:
-----------------
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'jakarta.sql.DataSource' (OnClassCondition)

Negative matches:
-----------------
   MongoAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class
           'com.mongodb.MongoClient' (OnClassCondition)

Positive matchesが実際に登録されたAutoConfiguration、Negative matchesが条件を満たさずスキップされたものです。「なぜこのBeanがないのか」を探すときはNegative matchesを確認するのがコツです。

AutoConfigurationの適用順序を制御する

複数のAutoConfigurationが依存し合う場合、適用順序が重要になります。Spring Boot 3.xでは@AutoConfigurationアノテーションの属性として指定するのが推奨です。

@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class MyRepositoryAutoConfiguration {
    // DataSourceAutoConfigurationの後に処理される
}

afterの他にbefore属性もあります。@AutoConfigureAfter / @AutoConfigureBeforeという単体アノテーション形式も後方互換として残っていますが、Spring Boot 3.xプロジェクトでは属性形式を使うほうがすっきりします。

数値で順序を指定したい場合は@AutoConfigureOrderが使えます。Spring Boot組み込みのAutoConfigurationより先に処理させたい場合などにOrdered.HIGHEST_PRECEDENCEなどの定数と組み合わせて指定します。

カスタムAutoConfigurationを作る

最後に実際にカスタムAutoConfigurationを作ってみましょう。今回はGreetingServiceというシンプルなBeanを自動登録する例です。

// GreetingServiceのインターフェースと実装
public interface GreetingService {
    String greet(String name);
}

public class DefaultGreetingService implements GreetingService {
    public String greet(String name) { return "Hello, " + name + "!"; }
}

AutoConfigurationクラスはこうなります。

@AutoConfiguration
public class GreetingAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public GreetingService greetingService() {
        return new DefaultGreetingService();
    }
}

このクラスをMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsに登録します。

com.example.autoconfigure.GreetingAutoConfiguration

これだけです。別のプロジェクトからこのJARを依存に追加するだけで、GreetingServiceのBeanが自動的に登録されます。ユーザーが自分でGreetingServiceを定義した場合は@ConditionalOnMissingBeanによって自動登録がスキップされます。

まとめ

問題が起きたらまずdebug=trueでConditions Evaluationレポートを確認するのが一番の近道です。Negative matchesを見れば「なぜBeanが登録されなかったのか」がすぐわかります。カスタムAutoConfigurationが作れるようになると、チーム共通の設定をStarterとしてまとめるという発展もできます。

Spring Boot Starterとは何かを理解する@Configurationアノテーションとは何かも合わせて読んでみてください。環境ごとにAutoConfigurationの挙動を切り替えたい場合はProfileを使った環境別設定も参考になります。