人人都会AI编程

11.4 条件注解:@Conditional 系列注解与场景化装配

更新时间:2026-07-11

在真实的项目中,并不是所有 Bean 都需要在任何环境下被创建。你可能需要“仅当某个类存在于 classpath 下时才启用某个配置”“仅在缺少特定 Bean 时提供一个默认实现”,或“仅在某个属性值为特定值时加载一组相关组件”。Spring 提供了一套强大的 条件注解(@Conditional 系列) 来实现这种按需、按场景的装配能力,使应用能够自适应地调整自身的内部构成。

11.4.1 条件装配的本质

条件装配的核心思想是:容器在注册 Bean 定义时,先对预设的条件进行判断,只有满足条件时,才会将对应的 Bean 定义加载到容器中。这与传统的 if-else@Profile 不同,条件可以基于更细粒度的环境信息——类是否存在、Bean 是否已定义、系统属性、环境变量、甚至自定义的任意逻辑。

Spring 为这套机制提供了统一的注解:@Conditional。它的 value 属性需要指定一个或多个 Condition 接口的实现类,该接口只有一个 matches 方法:

public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
  • ConditionContext 提供了运行时环境的所有关键信息:BeanDefinitionRegistryConfigurableListableBeanFactoryEnvironmentResourceLoaderClassLoader 等。
  • AnnotatedTypeMetadata 是被标注的类或方法的元数据,可以访问其上的注解属性。

只有当 matches 返回 true 时,带有该条件的配置类、@Bean 方法或组件才会被注册。

11.4.2 Spring Boot 内置的常用条件注解

@Conditional 本身是一个通用工具,实际开发中更常用的是 Spring Boot 在 org.springframework.boot.autoconfigure.condition 包下提供的一系列派生注解,它们将常见的判断逻辑封装好了,开箱即用。

1. 基于类的存在性

  • @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")value = {DataSource.class}:当 classpath 中存在指定的类时条件成立。最常用于自动配置模块:例如,只有引入 HikariCP 的 JAR 时才装配连接池的配置。
  • @ConditionalOnMissingClass(value = "some.package.OptionalLib"):当 classpath 中缺失指定类时条件成立。

2. 基于 Bean 的存在性

  • @ConditionalOnBean(type = "javax.sql.DataSource"):当容器中已经存在指定类型的 Bean 时条件成立。适用于“如果用户自定义了某组件,就不再使用默认组件”的场景。
  • @ConditionalOnMissingBean(name = "myService"):当容器中不存在指定名称或类型的 Bean 时条件成立。最经典的用法是提供默认的 ObjectMapperCacheManager 等,允许用户覆盖。

3. 基于属性或资源

  • @ConditionalOnProperty(prefix = "app.feature", name = "enabled", havingValue = "true", matchIfMissing = false):当配置文件中存在指定属性且值符合预期时生效。这是实现功能开关最常用的注解,比如“仅在 cache.enabled=true 时才启用缓存”。
  • @ConditionalOnResource(resources = "classpath:myconfig.properties"):当 classpath 下存在指定资源文件时条件成立。

4. 其他特定条件

  • @ConditionalOnExpression("${app.env} == 'prod'"):基于 SpEL 表达式的计算结果,可以组合多个条件。
  • @ConditionalOnJava / @ConditionalOnJndi / @ConditionalOnSingleCandidate 等,满足特定场景需求。

这些注解可单独使用,也可组合叠加。当一个类或 @Bean 方法同时标注多个条件注解时,默认是逻辑“与”的关系,即全部条件满足才会装配。

11.4.3 场景化装配实战

场景一:提供可被用户覆盖的默认组件

假设我们的基础服务模块需要提供一个默认的 IdGenerator,但当用户自己定义了一个 IdGenerator Bean 时,应优先使用用户的实现:

@Configuration
public class IdGeneratorAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(IdGenerator.class)
    public IdGenerator defaultIdGenerator() {
        return new SnowflakeIdGenerator();
    }
}

这样,如果用户在自己的配置中通过 @Bean 提供了一个 UUIDGenerator,默认的雪花算法生成器就不会被加载,不会产生冲突,也不需要额外的 @Primary 或排除操作。

场景二:基于外部开关启用功能模块

某些功能可能仅在生产环境中启用,或者需要显式地在配置中打开。比如一个发送短信通知的服务:

@Configuration
@ConditionalOnProperty(prefix = "notification.sms", name = "enabled", havingValue = "true")
public class SmsNotificationConfiguration {

    @Bean
    public SmsSender smsSender() {
        return new AliyunSmsSender();
    }
}

application.yml 中:

notification:
  sms:
    enabled: true  # 只有此处为 true 时,整个 SmsSender 及配套配置才会生效

通过这种方式,我们可以将应用做成“可选模块集合”,运维人员或部署流程只需调整配置值,便可控制功能的启用与关闭,无需重新打包。

场景三:根据依赖的类是否存在自动适配底层实现

这是在书写与多个库兼容的自动配置时的常用模式。例如,我们的应用可能同时支持 Redis 和 Caffeine 两种缓存,只加载实际引入的那个:

@Configuration
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
public class RedisCacheConfiguration {
    @Bean
    public CacheManager redisCacheManager(RedisConnectionFactory factory) {
        // 构建 RedisCacheManager
    }
}

@Configuration
@ConditionalOnClass(Caffeine.class)
@ConditionalOnMissingBean(CacheManager.class)
public class CaffeineCacheConfiguration {
    @Bean
    public CacheManager caffeineCacheManager() {
        // 构建 CaffeineCacheManager
    }
}

由于加入了 @ConditionalOnMissingBean(CacheManager.class),这两个配置是互斥且安全的——如果用户自己定义了 CacheManager,两者都不会生效。

11.4.4 自定义条件注解

当内置注解不能满足需求时,可以通过实现 Condition 接口定制自己的条件逻辑。例如,假设我们需要一个“仅在操作系统是 Windows 时才创建某组件”的条件:

首先,实现 Condition 接口:

public class OnWindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String os = context.getEnvironment().getProperty("os.name");
        return os != null && os.toLowerCase().contains("win");
    }
}

然后,定义一个快捷注解以简化使用:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnWindowsCondition.class)
public @interface ConditionalOnWindows {
}

在配置类上使用:

@Configuration
@ConditionalOnWindows
public class WindowsSpecificConfig {
    // 仅在 Windows 环境下生效的 Bean
}

11.4.5 理解条件装配的优先级与顺序

条件注解在容器处理配置类时即被评估,与 Bean 的创建顺序无关。需要特别注意:

  • 条件评估阶段:发生在解析配置类、处理 @Bean 方法的过程中,早于任何 Bean 的实例化。因此在 matches 方法中通过 BeanFactory 查找 Bean 时,只能查到已注册的 Bean 定义,而不是已实例化的 Bean。
  • @ConditionalOnBean 的“依赖顺序”陷阱:条件中依赖的 Bean 必须来自另一个已被处理的配置类,否则可能因为处理顺序导致条件判断失败。通常可通过 @AutoConfigureAfter@AutoConfigureBefore 来控制自动配置类的顺序,或者在用户手动配置时确保排序。
  • 组合条件:如果一个 @Bean 方法同时有类级条件(如 @ConditionalOnClass 标注在配置类上)和方法级条件,两者都必须满足。这提供了分层控制能力——配置类负责粗粒度的准入,方法负责细粒度的决策。

11.4.6 实际工程中的价值

条件注解让应用的架构从“编译期决定”转变为“部署时决定”,它带来的好处是实质性的:

  • 按需打包:同一个服务包,通过不同的配置和 profile 可以合法地运行在不同环境,不需要为每种环境维护单独的分支。
  • 灵活的扩展机制:框架或基础库的开发者可以利用条件注解提供默认实现,同时允许使用者零侵入地覆盖。
  • 渐进式集成:在新功能上线时,可以先关闭对应的条件,仅在少量节点上开启验证,确认无误后再全量打开,实现安全的灰度发布。
  • 简化集成测试:测试环境中可以轻松排除某些重量级组件(如消息队列、缓存),只加载核心业务 Bean,加速测试启动。

掌握条件注解及其场景化装配策略,是走向自动化配置和编写健壮、灵活 Spring 应用的必备能力。在下一节中,我们将深入探讨 Spring Boot 自动配置的原理,你会发现条件注解正是其中最为关键的拼图之一。