title: Spring Boot 条件注解体系 description: @Conditional 系列注解完全指南,覆盖所有条件注解的用法、组合条件与面试高频追问。

Spring Boot 条件注解体系

候选人小周在面试字节P6时,被问到:

"Spring Boot 的自动配置是怎么控制某个 Bean 在特定条件下才注册的?"

小周说:"用 @Conditional 吧?"面试官追问:"@ConditionalOnProperty 和 @ConditionalOnBean 有什么区别?"小周说:"一个是检查配置,一个是检查 Bean?"

面试官继续追问:"那 @ConditionalOnBean 和 @ConditionalOnMissingBean 呢?它们的执行顺序是什么?"小周彻底卡住了。

面试官又问:"你知道 @ConditionalOnExpression 吗?什么时候用它?"小周答不上来。

【面试官心理】

这道题我用来测试候选人对 Spring Boot 自动配置机制的理解深度。能说出 @Conditional 名字的占 60%,能说出常见条件注解的占 30%,能讲清楚执行顺序和组合条件的只有 10%。这道题是 P6 和 P5 的分水岭。

一、条件注解全景图 🔴

1.1 Spring Boot 2.x 条件注解一览

Spring Boot 在 @Conditional 基础上扩展了大量条件注解,形成了完整的条件体系:

条件注解检查条件面试频率
@ConditionalOnBean容器中存在指定 Bean必考
@ConditionalOnMissingBean容器中不存在指定 Bean必考
@ConditionalOnClassclasspath 中存在指定类高频
@ConditionalOnMissingClassclasspath 中不存在指定类高频
@ConditionalOnProperty配置属性满足条件必考
@ConditionalOnResource指定资源文件存在了解
@ConditionalOnWebApplication是 Web 应用高频
@ConditionalOnNotWebApplication非 Web 应用了解
@ConditionalOnExpressionSpEL 表达式为 true中频
@ConditionalOnJava运行在指定 Java 版本了解
@ConditionalOnCloudPlatform运行在指定云平台了解

1.2 @ConditionalOnBean vs @ConditionalOnMissingBean

这是最容易混淆的一对。来看一个实战场景:

@Configuration
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource autoDataSource() {
        return new HikariDataSource();
    }
}

@ConditionalOnMissingBean 的语义:只有在容器中不存在 DataSource 类型的 Bean 时,才注册这个自动配置的 DataSource。

这意味着:用户自定义的 DataSource Bean 优先级更高,会自动配置的覆盖掉。

@Configuration
public class MyConfig {
    // 用户自定义的 DataSource——自动配置会被跳过
    @Bean
    public DataSource dataSource() {
        return new DruidDataSource();
    }
}
⚠️

最容易踩的坑:@ConditionalOnMissingBean 检查的是当前已注册的 Bean,而不是所有会被注册的 Bean。如果用户定义的 Bean 和自动配置的 Bean 在同一个配置类中,顺序很重要。

1.3 执行顺序的问题

@Configuration
public class BadConfig {

    // 这个 Bean 会先被检查
    @Bean
    public DataSource dataSource() {
        return new DruidDataSource();  // 用户自定义
    }

    // 这个 @ConditionalOnMissingBean 已经被满足了!
    // 因为上面的 dataSource() 已经执行了
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource autoDataSource() {
        return new HikariDataSource();  // 自动配置
    }
}

在同一个配置类中,Bean 方法按定义顺序执行,后面的 @ConditionalOnMissingBean 检查的是前面已经注册过的 Bean。

正确做法是:把自动配置和用户配置分离到不同的类中,让 Spring Boot 的 @AutoConfigureAfter 机制来控制顺序。

二、@ConditionalOnClass 的底层原理 🟡

2.1 不是真正加载类

很多人以为 @ConditionalOnClass 是通过 Class.forName 加载类,实际上不是:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    Class<?>[] value() default {};
    String[] name() default {};
}
// OnClassCondition 实际上是通过 ASM 检查字节码
class OnClassCondition extends SpringBootCondition {
    @Override
    public ConditionOutcome[] getMatchOutcomes(ConditionContext context, ... {
        // 使用 ClassDescriptor 检查类是否存在
        // 不需要真正加载类,避免 ClassNotFoundException
    }
}

这样做的好处:即使 classpath 中没有某个类,JVM 也不会抛出 ClassNotFoundException。因为检查字节码不需要真正加载类到 JVM。

2.2 实际用法

@Configuration
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
public class EmbeddedTomcatConfiguration {
    // 只有 classpath 中存在 Tomcat 相关的类时才会加载
}

三、@ConditionalOnProperty 深度解析 🟡

3.1 完整参数

@Configuration
@ConditionalOnProperty(
    prefix = "spring.datasource.hikari",
    name = "enabled",
    havingValue = "true",    // 期望的值
    matchIfMissing = true    // 如果配置项不存在时的默认行为
)
public class HikariDataSourceConfiguration {
    // ...
}
参数说明
prefix配置前缀
name配置名(不含前缀)
havingValue期望的值,只有匹配才生效
matchIfMissing配置项不存在时是否默认为匹配
relaxedNames是否支持松散命名(默认 true)

3.2 松散命名支持

# 下面三种写法等价
spring.datasource.hikari.enabled: true
spring.datasource.hikariEnabled: true
spring.datasource.hikariEnabled: true

relaxedNames = true 时支持驼峰和横线分隔的任意组合。

3.3 常用场景

// 只有显式开启时才配置 Redis
@ConditionalOnProperty(
    prefix = "spring.redis",
    name = "enabled",
    havingValue = "true"
)

// 默认为启用(matchIfMissing = true)
@ConditionalOnProperty(
    prefix = "myapp.cache",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true  // 没配就默认启用
)

// 支持多值匹配
@ConditionalOnProperty(
    prefix = "spring.profiles",
    name = "active",
    havingValue = "dev",
    matchIfMissing = false
)

四、组合条件 🟡

4.1 @ConditionalOnBean + @ConditionalOnProperty

实际项目中,最常见的组合是:既要检查 Bean 是否存在,又要检查配置属性:

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnProperty(prefix = "http.client", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(HttpClientProperties.class)
public class HttpClientAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate customRestTemplate(HttpClientProperties properties) {
        return RestTemplateBuilder.create()
            .setConnectTimeout(properties.getConnectTimeout())
            .setReadTimeout(properties.getReadTimeout())
            .build();
    }
}

4.2 @ConditionalOnExpression

当条件判断需要更灵活的逻辑时,使用 SpEL 表达式:

@Configuration
@ConditionalOnExpression(
    "${myapp.feature.enabled:false} && " +
    " '${myapp.environment}'.equals('prod')"
)
public class ProductionOnlyConfiguration {
    // 只有 feature.enabled=true 且环境是 prod 时才生效
}
@Configuration
@ConditionalOnExpression(
    "#{${myapp.max_connections:1000} > 500}"
)
public class HighConcurrencyConfiguration {
    // 支持引用配置属性并进行运算
}
💡

@ConditionalOnExpression 是条件注解中功能最强大的,但也是最容易被滥用的。SpEL 表达式在启动时求值,有一定的性能开销,而且 SpEL 的语法错误很难排查。建议先用 @ConditionalOnProperty 解决,必要时再用 @ConditionalOnExpression。

五、Spring Boot 2.x 到 3.x 的演进 🟢

注解Spring Boot 2.xSpring Boot 3.x 变化
@ConditionalOnClass广泛使用大量使用(Servlet → Jakarta)
@ConditionalOnWebApplication使用改名为 @ConditionalOnWebApplication(仍存在)
@ConditionalOnBean广泛使用新增 @ConditionalOnSingleCandidate(更精确)

Spring Boot 3.x 新增了 @ConditionalOnSingleCandidate,用于更精确地判断容器中是否只有一个指定类型的候选 Bean:

// Spring Boot 3.x
@Bean
@ConditionalOnSingleCandidate(DataSource.class)
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
    // 只有容器中恰好只有一个 DataSource 候选 Bean 时才生效
}

六、❌ 错误示范

6.1 混淆 OnBean 和 OnMissingBean

候选人原话:"@ConditionalOnBean 就是当 Bean 存在时生效,@ConditionalOnMissingBean 是当 Bean 不存在时生效。"

问题诊断

  • 理解正确,但不知道执行顺序的问题
  • 不知道 @ConditionalOnMissingBean 在同一配置类中的特殊行为

面试官内心 OS:"这个候选人知道两者的表面区别,但不知道 Spring Boot 在处理 Bean 注册顺序时的坑。实践中这个问题非常容易引发排查困难。"

6.2 以为 OnClass 会加载类

候选人原话:"@ConditionalOnClass 是通过 Class.forName 检查类是否存在,不存在就跳过这个配置。"

问题诊断

  • 知道目的但不知道实现原理
  • 实际上用的是 ASM 字节码检查,不是 Class.forName

6.3 matchIfMissing 滥用

候选人原话:"配置属性没配的时候,默认就是不生效。"

问题诊断

  • 不理解 matchIfMissing 的作用
  • 以为默认行为是"不匹配"

七、面试标准回答

7.1 P5 级别

"Spring Boot 的条件注解用于控制配置类或 Bean 是否生效。常用注解有 @ConditionalOnBean(Bean 存在时)、@ConditionalOnMissingBean(Bean 不存在时)、@ConditionalOnClass(类存在时)、@ConditionalOnProperty(配置满足条件时)。"

7.2 P6 级别

"@ConditionalOnBean 检查容器中是否存在指定类型的 Bean;@ConditionalOnMissingBean 检查不存在时才生效,目的是让用户自定义 Bean 覆盖自动配置。

@ConditionalOnClass 通过 ASM 字节码检查类是否存在,不会触发 ClassNotFoundException。@ConditionalOnProperty 支持 prefix + name + havingValue + matchIfMissing 的组合,matchIfMissing = true 表示配置项不存在时默认匹配。

组合条件使用时要注意:在同一配置类中,Bean 方法按定义顺序执行,后面的 @ConditionalOnMissingBean 检查的是前面已注册的 Bean。建议把用户配置和自动配置分离到不同类中,用 @AutoConfigureBefore/After 控制顺序。"

7.3 P7 级别

"条件注解体系是 Spring Boot 自动配置的基石。我之前排查过一个典型的坑:用户在配置类中先定义了 DataSource Bean,然后又依赖了自动配置的另一个 DataSource Bean,@ConditionalOnMissingBean 判断时发现容器中已经有了,导致自动配置的 Bean 没生效。

Spring Boot 3.x 的 @ConditionalOnSingleCandidate 就是为了解决这类问题——它不仅检查 Bean 是否存在,还检查是否只有一个候选者,避免了误判。

最佳实践是:1. 自动配置类用 @ConditionalOnMissingBean 让用户覆盖;2. 组合条件尽量拆分成多个注解而非 SpEL 表达式;3. 避免在同一配置类中混合用户配置和自动配置;4. 使用 @AutoConfigureBefore/After 控制多个自动配置类之间的顺序。"

【面试官心理】

P7 的回答重点在于"排查过什么坑"和"最佳实践"。能说出 @ConditionalOnSingleCandidate 解决的是什么问题的候选人,说明他对 Spring Boot 3.x 的演进也有关注,这是加分项。