导读:本期聚焦于葵司创作的《Spring Boot 条件装配怎么实现?@Conditional 系列注解工作原理详解》,敬请观看详情。为什么同一个 Starter 引入项目后就能自动完成配置?答案藏在 Spring Boot 的条件装配机制里。本文围绕 @Conditional 注解及其衍生出的 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等一系列条件注解展开,先讲清楚 Condition 接口的匹配原理与加载时机,再逐个分析常用条件注解的适用场景和注意事项,最后手写一个自定义条件注解并演示如何配合 @EnableConfigurationProperties 与自动配置完成按需加载。理解了这套机制,你就能看懂自动配置源的执行逻辑,也能在自己的组件设计中实现灵活的开关控制。

Spring Boot 能做到开箱即用,靠的不仅仅是自动装配,还有一套精细的条件装配机制。当你在 pom 里引入某个 Starter,相关的配置类是否生效,并不是启动时全部加载,而是由一个个条件注解来决定。@Conditional 就是这套机制的核心,它衍生出的 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等注解,构成了 Spring Boot 自动配置的判断基础。这篇文章把这套机制拆开讲清楚,并带你手写一个自定义条件注解。

Spring Boot 条件装配怎么实现?@Conditional 系列注解工作原理详解

一、条件装配的底层原理:Condition 接口如何工作

所有条件注解的源头都是 @Conditional,它的定义非常简单,只有一个 value 属性,接收实现了 Condition 接口的类。Spring 在注册 Bean 定义之前,会先调用 Condition 的 matches 方法,返回 true 才继续注册,返回 false 则直接跳过这个配置类或 Bean 方法。

关键的执行入口在 ConditionEvaluator 中,它由 ConfigurationClassParser 调用。也就是说,条件判断发生在配置类解析阶段,比 Bean 实例化要早得多。这一点很重要:条件注解里拿不到已经创建好的 Bean 实例,只能拿到 ConditionContext 提供的元信息,包括环境变量、BeanFactory、类加载器等。

public class OnLinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 通过环境信息判断当前操作系统
        String os = context.getEnvironment()
                .getProperty("os.name", "").toLowerCase();
        return os.contains("linux");
    }
}

@Configuration
@Conditional(OnLinuxCondition.class)
public class LinuxOnlyConfig {
    @Bean
    public TaskScheduler linuxScheduler() {
        return new TaskScheduler();
    }
}

还有一个容易被忽略的细节:Condition 分为普通判断和配置类判断两类,即 ConditionConfigurationCondition。后者的 getConfigurationPhase 方法可以指定判断发生在解析阶段还是注册阶段。Spring Boot 内置的条件注解大多通过 OnClassConditionOnBeanCondition 等类实现,其中类路径判断必须发生在解析阶段,因为如果类都不存在,配置类根本无法完成加载。

二、常用条件注解的场景与区别

Spring Boot 在 @Conditional 基础上派生了十几个注解,各自对应不同的判断维度,理解它们各自的适用场景是用好条件装配的前提。

@ConditionalOnClass 判断类路径是否存在某个类,这是 Starter 最常用的方式。比如 spring-boot-starter-data-redis 的自动配置类上有 @ConditionalOnClass(RedisOperations.class),只有引入了 Redis 客户端依赖,这份配置才会生效。需要注意的是,被判断的类如果写在方法返回值或字段类型上,一旦类不存在会直接抛 ClassNotFoundError,所以推荐把类名写成字符串形式 name = "com.xxx.CacheClient",或者用全字符串配置,避免字节码层面的强依赖。

@ConditionalOnMissingBean 是实现用户配置覆盖默认配置的关键。自动配置类中定义默认 Bean 时加上它,一旦用户自己声明了同类型 Bean,默认的就让位。配合 search 属性还能控制搜索范围,默认 SearchStrategy.ALL 会搜索所有祖先容器,在父子容器场景(如传统的 Spring MVC + Spring Boot 组合)要特别小心误判。

@Configuration
public class CacheAutoConfiguration {

    @Bean
    // 用户没有自己定义 CacheManager 时才注册默认实现
    @ConditionalOnMissingBean(CacheManager.class)
    public CacheManager defaultCacheManager() {
        return new ConcurrentMapCacheManager();
    }

    @Bean
    // 配置文件中 cache.type=redis 且值存在时才生效
    @ConditionalOnProperty(name = "cache.type", havingValue = "redis")
    public RedisCacheManager redisCacheManager() {
        return new RedisCacheManager();
    }
}

@ConditionalOnProperty 通过配置文件做开关,是最灵活的一个。它的 matchIfMissing 属性决定了配置项不存在时的行为,默认 false。写自定义 Starter 时,建议为关键功能提供 xxx.enabled 开关,并明确设置 matchIfMissing 的值,避免用户没配置时行为不明确。

其余注解也各有分工:@ConditionalOnBean@ConditionalOnMissingBean 依赖 Bean 定义顺序,官方并不推荐在自动配置中大量使用;@ConditionalOnWebApplication@ConditionalOnNotWebApplication 区分 Web 环境;@ConditionalOnExpression 支持 SpEL 表达式做复合判断;@ConditionalOnJava 可以限定 JDK 版本。实际开发中经常需要多个条件组合,直接叠加注解即可,它们之间是与的关系。

注解判断依据典型场景
@ConditionalOnClass类路径是否存在某类Starter 自动配置守门
@ConditionalOnMissingBean容器中是否缺少某 Bean提供可覆盖的默认实现
@ConditionalOnProperty配置文件属性值功能开关
@ConditionalOnWebApplication是否为 Web 环境环境相关 Bean 注册
@ConditionalOnResource资源文件是否存在依赖外部配置文件的组件

三、实战:手写一个自定义条件注解

如果内置注解不能满足需求,比如需要根据注册表键值或者某个外部服务状态来决定是否装配,就需要自定义。做法分三步:写 Condition 实现类、写组合注解、应用到配置类上。下面以常见的多数据源场景为例,根据配置文件中的 db.type 决定装配哪种数据源。

// 第一步:定义条件判断逻辑
public class DbTypeCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 从注解属性中拿到期望的数据库类型
        Map<String, Object> attrs = metadata.getAnnotationAttributes(
                ConditionalOnDbType.class.getName());
        String expected = (String) attrs.get("value");
        // 从环境变量或配置文件读取实际类型,未配置时默认 mysql
        String actual = context.getEnvironment()
                .getProperty("db.type", "mysql");
        return expected.equalsIgnoreCase(actual);
    }
}

// 第二步:定义组合注解,比直接用 @Conditional 更直观
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DbTypeCondition.class)
public @interface ConditionalOnDbType {
    String value();
}

// 第三步:应用到配置类
@Configuration
public class DataSourceConfig {

    @Bean
    @ConditionalOnDbType("mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://127.0.0.1:3306/app")
                .build();
    }

    @Bean
    @ConditionalOnDbType("postgres")
    public DataSource postgresDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:postgresql://127.0.0.1:5432/app")
                .build();
    }
}

写自定义条件时有几个坑要避开。第一,matches 方法中不要做耗时操作,它会在启动阶段被多次调用,拖慢启动速度。第二,注解属性通过 metadata.getAnnotationAttributes 获取时,返回的键是注解定义的属性名,不是随意命名的。第三,如果你的判断依赖其他 Bean 的注册情况,考虑实现 ConfigurationCondition 并把阶段设为 REGISTER_BEAN,否则可能因为时序问题得到不可预期的结果。

最后补充一个排查技巧:当条件装配结果不符合预期时,不要靠猜。在启动类上开启 debug 模式,也就是在配置文件中加上 debug=true,或在启动参数中加 --debug,启动日志会输出一份条件评估报告,明确列出哪些自动配置类因为哪个条件不满足而未生效,这是定位条件装配问题最直接的手段。配合 ConditionEvaluationReport 还可以在代码中拿到完整报告做二次分析。

总的来说,条件装配是 Spring Boot 自动配置的基石,理解 Condition 的执行时机、各条件注解的判断维度以及自定义扩展方式,不仅能让你看懂官方 Starter 的源码,也能在自己设计可插拔组件时游刃有余。

Spring Boot条件装配@Conditional修改时间:2026-09-14 04:40:40

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260914/56462.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。