Springの@Conditionalによる条件付きBean登録

@Conditionalの基本構造

Spring Framework 4より導入された@Conditionalは、指定した条件に応じてBeanの登録可否を制御するメタアノテーションです。対象のConditionインターフェースの実装に基づき、コンテナ起動時の動的な判定が可能になります。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

判定ロジックの核となるConditionインターフェースは単一のメソッドを持ちます。返り値がtrueの場合に対象Beanが有効化されます。

public interface Condition {
    boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta);
}

実行環境に応じたBean選択の実装

OS種別で異なるBeanを登録する具体例を示します。まず、各プラットフォーム用の判定クラスを作成します。

macOS環境判定クラス

public class MacOsRequirement implements Condition {
    @Override
    public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {
        String osName = ctx.getEnvironment().getProperty("os.name");
        return osName != null && osName.toLowerCase().contains("mac");
    }
}

Unix系環境判定クラス

public class UnixRequirement implements Condition {
    @Override
    public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {
        String osName = ctx.getEnvironment().getProperty("os.name");
        if (osName == null) return false;
        String lower = osName.toLowerCase();
        return lower.contains("nix") || lower.contains("nux") || lower.contains("aix");
    }
}

設定クラスでの適用

@Configuration
public class PlatformAwareConfig {

    @Bean
    @Conditional(MacOsRequirement.class)
    public Developer macDeveloper() {
        return new Developer("Swift Developer", Set.of("Swift", "Objective-C"));
    }

    @Bean
    @Conditional(UnixRequirement.class)
    public Developer linuxDeveloper() {
        return new Developer("Kernel Hacker", Set.of("C", "Rust", "Go"));
    }
}

クラスレベルでの条件指定

@Conditionalは@Configurationクラスそのものに付与可能です。この場合、クラス内の全@Beanメソッドに対して一括で判定が適用されます。

@Conditional(ProductionProfile.class)
@Configuration
public class ExternalServiceConfig {

    @Bean
    public PaymentGateway paymentAdapter() {
        return new StripeAdapter();
    }

    @Bean
    public NotificationService alertDispatcher() {
        return new SlackNotificationService();
    }
}

クラスレベルで複数のConditionを指定した場合、いずれかがfalseを返せばクラス全体が無効化されます。すべての条件がtrueを返す必要がある点に注意が必要です。

複合条件の構成パターン

複数の条件を論理演算で組み合わせる場合、個別のCondition実装をラップする方法が推奨されます。

public class CompositeRequirement implements Condition {
    @Override
    public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {
        boolean hasJdbcUrl = ctx.getEnvironment().containsProperty("spring.datasource.url");
        boolean isNotTest = !Arrays.asList(ctx.getEnvironment().getActiveProfiles()).contains("test");
        return hasJdbcUrl && isNotTest;
    }
}

検証用の起動コード

public class ConditionalDemo {
    public static void main(String[] args) {
        var appCtx = new AnnotationConfigApplicationContext(PlatformAwareConfig.class);
        
        Arrays.stream(appCtx.getBeanDefinitionNames())
              .filter(name -> !name.startsWith("org.springframework"))
              .forEach(System.out::println);
    }
}

実行時に-Dos.name=LinuxなどのJVM引数を渡すことで、異なるBeanセットが有効化されることを確認できます。

タグ: Spring Framework Conditional Bean Java Configuration IoC Container Spring Boot

10月29日 00:26 投稿