SpringBootで設定値に応じたBean登録を切り替える方法　@ConditionalOnPropertyと@Conditional

# はじめに

こんにちは、GxPの南舘です！
この記事は[グロースエクスパートナーズ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/gxp) の8日目です。

今年の春に入社し医療系システムの看護業務支援アプリの開発に携わっています。
先日、先輩からこんな依頼がありました。
「顧客の要望に応じて設定値をymlファイルに持っているから、それを基にクラスのBean登録を切り替えられる方法を調べてほしい」
「基本機能として使う予定だけれど、病院によっては不要な場合もある機能があり、サービスの起動中にクラスの実行を切り替えられると便利だよね」という話でした。

その際に学んだ `@ConditionalOnProperty` と `@Conditional` というアノテーションについて、調査内容を記事としてまとめてみました。

:::note
これから紹介するアノテーションを使用するメリット

  - 柔軟性の向上 
    - `@ConditionalOnProperty`と`@Conditional`を活用することで、アプリケーションの起動時に柔軟なコンポーネントの有効化/無効化が可能になります。例えば、病院のような顧客要件が異なる場面では、各施設ごとの要望に応じたサービスの最適化が可能です。

  - コードが簡潔になり運用時の設定切り替えが容易。
    - プロパティを変更するだけでBeanの有効化/無効化を切り替えられるため、コードを修正せずに運用中のシステムを調整できます。
  - パフォーマンスの向上
    - 不要なBeanを無効化することで、実行時の初期化処理などをスキップできるため、パフォーマンスが上がります。

:::


## 概要

Spring Bootの`@ConditionalOnProperty`及び`@Conditional`を使用して、特定(今回はymlファイル内のboolean値)のプロパティ値によるBeanの登録切り替えを試みました。その際の注意点や設定方法、テスト結果をまとめています。
- プロパティが１つの場合
    -  `@ConditionalOnProperty`を使用する。
- プロパティが複数の場合
    - `@Conditional`を使用する。

## 使用環境
- 言語: Java 11
- フレームワーク: Spring Boot 2.2.0
- ビルドツール: Gradle 5.6.3
- その他: IntelliJ IDEA 2024.1(Ultimate Edition)

 

# `@ConditionalOnProperty`の実装例

 **1. `application.yml` にプロパティを追加。**
 
```yaml
app:
  test:
    information:
        value: true
```

 **2. クラスにアノテーションを付与。ymlファイルのプロパティ値を取得。**
 
```java
@ConditionalOnProperty(
    prefix = "app.test.information", //設定値の階層構造（親のパス）を指定する部分
    name = "value",                  // 階層構造以下の設定値名を指定する
    havingValue = "true"             // 期待値
)
public class Sample {
    // 実装内容
}
```

**3. 実行後にBeanの一覧を表示する方法が分からなかったため、確認用のログをアノテーションを付与したクラス内に仕込む。**

```java
log.info(" ConditionalOnProperty テスト");
```

プロパティ値が両方falseの際はログが出力されない。
プロパティ値が両方true、もしくはどちらかがtrueの際にはログが出力されている。
   
   結果は以下の条件分岐表の通りになりました。

## 条件分岐表（ConditionalOnProperty）
| プロパティ値 | `""` | `"true"` | `"false"` | `"foo"` |
| --- | --- | --- | --- | --- |
| `"true"` | true | true | false | false |
| `"false"` | false | false | true | false |
| `"foo"` | true | false | false | true |

※ヘッダー行の2〜5列目は`havingValue`の設定値です。

参照元：https://spring.pleiades.io/spring-boot/api/java/org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.html

# `@Conditional`の実装例


**1.`application.yml` にプロパティを追加。**
   
```yaml
app:
  test:
    information:
        testValue1: true
        testValue2: true
```


 **2. 条件クラスの実装。**

Conditionインターフェースを実装し、ConditionContextを使用してプロパティを取得。取得した値を元に条件を判断します。

```java
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

@Slf4j
public class SampleCondition implements Condition {
    private boolean testValue1;
    private boolean testValue2;

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // プロパティを環境変数から取得
        Environment env = context.getEnvironment();
        // getPropertyメソッド 第１引数:プロパティのキーは必須、ドット区切りで階層構造を表現。第２引数:デフォルト値はオプション。
        this.testValue1 = Boolean.parseBoolean(env.getProperty("app.test.information.testValue1", "true"));
        this.testValue2 = Boolean.parseBoolean(env.getProperty("app.test.information.testValue2", "true"));

        // 条件に応じてBeanを登録するかどうか判断
        return testValue1 || testValue2;
    }
        public boolean testValue1() {
        return testValue1;
    }
        public boolean testValue2() {
        return testValue2;
    }
}
```


 **3. Sampleクラスでの使用。**

最後に、`@Conditional`を使用して条件付きでBeanを登録します。

```java
@Configuration
@Conditional(SampleCondition.class)
public class Sample {
    // タスクの設定
}
```

**解説**  
- **Sampleクラス**に`@Conditional`を付与。
- 条件が満たされる場合のみ、このクラスのBeanが登録されます。

## 条件分岐表（ConditionalOnProperty）

| `testValue1` | `testValue2` | (`デフォルト値1`) | (`デフォルト値2`) | 結果 (`matches`) |
| --- | --- | --- | --- | --- | 
| `true` | `true` | `true` | `true` | `true` | 
| `false` | `false` | `true` | `true` | `false` | 
| `null` | `null` | `true` | `false` | `true` |

**実装のポイント**


- Main以外のクラスで使用可能。

- 柔軟な条件定義: `Condition`インターフェースを利用することで、プロパティや環境変数に応じたカスタム条件を定義可能。


# 使い分けの基準
 - `@ConditionalOnProperty`を使用すべきケース
     - チェックするプロパティが1つ、単純な条件評価で十分な場合。
     - 例: 機能フラグのオン/オフを切り替えるシンプルなユースケース。
 - `@Conditional`を使用すべきケース
     - 複数のプロパティやカスタム条件が必要な場合。
     - プロパティの組み合わせを条件とする場合。

# さいごに
ここまで読んでいただきありがとうございました！
Javaで開発をする際の参考にしていただければと思います！

**参考リンク**

https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Conditional.html