@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セットが有効化されることを確認できます。