Spring @Conditional 注解源码深度解析:从 ConditionEvaluator 到条件化 Bean 注册

发布时间:2026/11/5 11:00:28

Spring @Conditional 注解源码深度解析:从 ConditionEvaluator 到条件化 Bean 注册 Spring Conditional 注解源码深度解析从 ConditionEvaluator 到条件化 Bean 注册【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter导读本文基于当前仓库 docs/Spring/clazz/Spring-Conditional.md 的源码阅读笔记深入剖析 Spring 框架Conditional条件注解的底层实现原理。Conditional是 Spring 条件化配置的基石也是 Spring Boot 自动装配ConditionalOnClass、ConditionalOnBean等赖以运转的核心机制。读完本文你将掌握Conditional的注解定义、Condition匹配器的执行流程、ConditionEvaluator.shouldSkip的两阶段跳过逻辑并能独立编写自定义条件配置。认识核心注解与接口Conditional 注解定义Conditional是一个作用于类型ElementType.TYPE和方法ElementType.METHOD的运行时注解它的唯一属性是多个条件匹配器类Target({ ElementType.TYPE, ElementType.METHOD }) Retention(RetentionPolicy.RUNTIME) Documented public interface Conditional { /** * 多个匹配器接口 */ Class? extends Condition[] value(); }它既可以标注在Configuration配置类上也可以标注在Bean方法上从而决定整个配置类或单个 Bean 方法是否参与容器初始化。Condition 匹配器接口Condition是一个函数式接口FunctionalInterface只有一个核心方法matchesFunctionalInterface public interface Condition { /** * 匹配,如果匹配返回true进行初始化,返回false跳过初始化 */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }方法签名中的两个参数是条件判断的全部信息来源ConditionContext context条件上下文封装了 BeanDefinition 注册表、BeanFactory、Environment 环境、资源加载器、类加载器等容器运行时信息AnnotatedTypeMetadata metadata注解元数据描述了当前被Conditional标注的类或方法上的注解信息。只要matches返回falseSpring 就会跳过对应配置类或 Bean 的初始化返回true则正常注册。两个关键参数ConditionContext 与 AnnotatedTypeMetadataConditionContext条件判断的运行环境public interface ConditionContext { /** * bean的定义 */ BeanDefinitionRegistry getRegistry(); /** * bean 工厂 */ Nullable ConfigurableListableBeanFactory getBeanFactory(); /** * 环境 */ Environment getEnvironment(); /** * 资源加载器 */ ResourceLoader getResourceLoader(); /** * 类加载器 */ Nullable ClassLoader getClassLoader(); }五个方法分别暴露了容器中五个维度的资源方法返回类型用途getRegistry()BeanDefinitionRegistry读取/注册 BeanDefinition判断某个 Bean 是否已定义getBeanFactory()ConfigurableListableBeanFactory操作 BeanFactory查询 BeanDefinition 等getEnvironment()Environment读取系统属性、环境变量、配置文件中的属性getResourceLoader()ResourceLoader加载资源配合ConditionalOnResource等场景getClassLoader()ClassLoader判断某个类是否存在于 classpath配合ConditionalOnClass场景唯一实现是内部类org.springframework.context.annotation.ConditionEvaluator.ConditionContextImpl。其构造方法会在创建时根据传入参数推导出完整的上下文信息public ConditionContextImpl(Nullable BeanDefinitionRegistry registry, Nullable Environment environment, Nullable ResourceLoader resourceLoader) { this.registry registry; this.beanFactory deduceBeanFactory(registry); this.environment (environment ! null ? environment : deduceEnvironment(registry)); this.resourceLoader (resourceLoader ! null ? resourceLoader : deduceResourceLoader(registry)); this.classLoader deduceClassLoader(resourceLoader, this.beanFactory); }从源码结构看registry直接透传beanFactory通过deduceBeanFactory(registry)推导environment、resourceLoader在显式传入时直接使用否则分别通过deduceEnvironment(registry)与deduceResourceLoader(registry)从注册表推导classLoader则由resourceLoader与beanFactory共同推导。也就是说即使调用方只传入一个BeanDefinitionRegistry容器也能把其余四项环境信息补齐。AnnotatedTypeMetadata注解元数据public interface AnnotatedTypeMetadata { /** * 获取所有注解 */ MergedAnnotations getAnnotations(); /** * 是否有注解 */ default boolean isAnnotated(String annotationName) { return getAnnotations().isPresent(annotationName); } /** * 获取注解的属性 */ Nullable default MapString, Object getAnnotationAttributes(String annotationName) { return getAnnotationAttributes(annotationName, false); } }这是一个元数据接口Spring 通过它对外暴露被评估的类/方法上标注了哪些注解、注解属性是什么。MergedAnnotations是 Spring 5.2 引入的合并注解视图能统一处理注解的AliasFor别名与元注解meta-annotation继承关系。isAnnotated与getAnnotationAttributes均为默认方法底层都委托给getAnnotations()。源码核心ConditionEvaluator.shouldSkip 两阶段跳过逻辑条件判断的核心入口是org.springframework.context.annotation.ConditionEvaluator#shouldSkip它决定了要不要跳过这个配置类或 Bean 的注册public boolean shouldSkip(Nullable AnnotatedTypeMetadata metadata, Nullable ConfigurationPhase phase) { if (metadata null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } if (phase null) { if (metadata instanceof AnnotationMetadata ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } ListCondition conditions new ArrayList(); // 获取注解 Conditional 的属性值 for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { // 序列化成注解 Condition condition getCondition(conditionClass, this.context.getClassLoader()); // 插入注解列表 conditions.add(condition); } } AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase null; if (condition instanceof ConfigurationCondition) { requiredPhase ((ConfigurationCondition) condition).getConfigurationPhase(); } // matches 进行验证 if ((requiredPhase null || requiredPhase phase) !condition.matches(this.context, metadata)) { return true; } } return false; }第一阶段没有标注 Conditional直接跳过shouldSkip的第一步是快速失败判断如果metadata为null或目标上没有标注Conditional注解直接返回false不跳过正常注册。第二阶段phase 为 null 时自动推导阶段ConfigurationPhase是ConfigurationCondition接口中定义的枚举标记条件在哪个阶段生效PARSE_CONFIGURATION配置类解析阶段在ConfigurationClassParser解析Configuration类时执行条件判断此时Bean方法尚未处理适用于决定某个配置类整体是否加载REGISTER_BEANBean 注册阶段在配置类解析完成后、Bean 注册时执行适用于决定某个Bean方法产生的 Bean 是否注册。当调用方没有显式传入phase时Spring 会根据metadata的类型自动推导若metadata是AnnotationMetadata且ConfigurationClassUtils.isConfigurationCandidate判定其为配置类候选即标注了Configuration或Component系列注解则按PARSE_CONFIGURATION阶段执行否则按REGISTER_BEAN阶段执行。第三阶段加载并排序所有 ConditionSpring 通过getConditionClasses(metadata)读取Conditional注解的value属性多个条件类然后用getCondition实例化每个条件类该过程会合并元注解即支持通过元注解间接标注Conditional。实例化后的条件列表会用AnnotationAwareOrderComparator.sort(conditions)排序——这意味着条件类可以通过实现Ordered接口或标注Order注解来控制判断先后顺序这一点在 Spring Boot 的组合条件场景中非常重要。第四阶段逐条执行 matches遍历排序后的条件列表对每个Condition调用matches若条件实现了ConfigurationCondition则取出其声明的requiredPhase只有当requiredPhase为null即普通Condition任何阶段都参与或与当前phase相等时才执行matches一旦某个条件matches返回falseshouldSkip立即返回true跳过注册所有条件都通过才返回false不跳过。也就是说Conditional的多个条件之间是AND关系——任意一个不满足整个配置即被跳过。调用链条件判断发生在 Bean 注册的最前面ConditionEvaluator.shouldSkip的调用点位于org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean——这是注册 Bean 时执行的第一个方法private T void doRegisterBean(ClassT beanClass, Nullable String name, Nullable Class? extends Annotation[] qualifiers, Nullable SupplierT supplier, Nullable BeanDefinitionCustomizer[] customizers) { AnnotatedGenericBeanDefinition abd new AnnotatedGenericBeanDefinition(beanClass); // 和条件注解相关的函数 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } // 省略其他 }流程非常清晰doRegisterBean先把目标类包装成AnnotatedGenericBeanDefinition紧接着调用shouldSkip(abd.getMetadata())此时phase为null由 Spring 自动推导。若返回true则直接return后续的 BeanDefinition 注册、依赖注入统统不再执行只有通过条件判断的 Bean 才会继续走完注册流程。可以推断shouldSkip的调用点不限于AnnotatedBeanDefinitionReader在ConfigurationClassParser解析配置类、ClassPathBeanDefinitionScanner扫描组件时同样会通过ConditionEvaluator做条件过滤这也正是Conditional能作用于扫描组件、配置类、Bean方法等多个场景的原因。官方测试用例验证ConfigurationClassWithConditionTestsSpring 官方针对该机制提供了专门的测试类org.springframework.context.annotation.ConfigurationClassWithConditionTests仓库笔记中摘录了其中conditionalOnMissingBeanMatch用例直接印证了条件不满足则跳过注册的完整行为Test public void conditionalOnMissingBeanMatch() throws Exception { AnnotationConfigApplicationContext ctx new AnnotationConfigApplicationContext(); ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class); ctx.refresh(); assertThat(ctx.containsBean(bean1)).isTrue(); assertThat(ctx.containsBean(bean2)).isFalse(); assertThat(ctx.containsBean(configurationClassWithConditionTests.BeanTwoConfiguration)).isFalse(); }配套的两个配置类与条件类Configuration static class BeanOneConfiguration { Bean public ExampleBean bean1() { return new ExampleBean(); } } Configuration Conditional(NoBeanOneCondition.class) static class BeanTwoConfiguration { Bean public ExampleBean bean2() { return new ExampleBean(); } } static class NoBeanOneCondition implements Condition { Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return !context.getBeanFactory().containsBeanDefinition(bean1); } }用例断言揭示的结论BeanOneConfiguration无条件注册bean1存在isTrueBeanTwoConfiguration标注了Conditional(NoBeanOneCondition.class)其条件逻辑是容器中不存在名为bean1的 BeanDefinition 时才匹配由于bean1已存在NoBeanOneCondition.matches返回falseshouldSkip返回true于是bean2不存在、连BeanTwoConfiguration这个配置类本身都没有被注册为 BeanisFalse。这组断言从最终 Bean 状态层面验证了实例化BeanTwoConfiguration时Spring 会去执行NoBeanOneCondition.matches方法返回false即整体跳过。实战自定义 Condition 实现条件化配置基于上面的源码机制自定义一个条件配置只需三步实现Condition接口编写matches逻辑在配置类或Bean方法上标注Conditional(你的条件类.class)交给AnnotationConfigApplicationContext加载并refresh()。一个可运行的完整示例基于官方测试类改写public class ConditionalDemo { public static class ExampleBean { } // 条件只有配置了 jdbc.url 属性时才生效 public static class OnJdbcUrlCondition implements Condition { Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment().containsProperty(jdbc.url); } } Configuration Conditional(OnJdbcUrlCondition.class) public static class JdbcConfiguration { Bean public ExampleBean exampleBean() { return new ExampleBean(); } } public static void main(String[] args) { AnnotationConfigApplicationContext ctx new AnnotationConfigApplicationContext(); // 不设置 jdbc.urlJdbcConfiguration 被跳过 ctx.register(JdbcConfiguration.class); ctx.refresh(); System.out.println(ctx.containsBean(exampleBean)); // false } }条件类中可以自由组合ConditionContext提供的五类资源按环境属性context.getEnvironment().getProperty(xxx)按 Bean 是否存在context.getBeanFactory().containsBeanDefinition(beanName)按类是否在 classpathcontext.getClassLoader().loadClass(com.xxx.Yyy)按资源是否存在context.getResourceLoader().getResource(classpath:xxx.xml)。如需控制条件执行阶段可让条件类实现ConfigurationCondition并覆写getConfigurationPhase()指定在PARSE_CONFIGURATION或REGISTER_BEAN阶段生效。延伸Spring Boot 对 Conditional 的体系化扩展理解了 Spring 框架层的Conditional与Condition之后再看 Spring Boot 的条件化自动装配就一目了然了。仓库中的 SpringBoot-ConditionalOnBean.md 一文专门剖析了 Spring Boot 在这套机制之上的完整扩展核心要点如下。一系列 ConditionalOnXxx 注解Spring Boot 在Conditional基础上衍生出一整套开箱即用的条件注解ConditionalOnBean、ConditionalOnClass、ConditionalOnCloudPlatform、ConditionalOnExpression、ConditionalOnJava、ConditionalOnJndi、ConditionalOnMissingBean、ConditionalOnMissingClass、ConditionalOnNotWebApplication、ConditionalOnProperty、ConditionalOnResource、ConditionalOnSingleCandidate、ConditionalOnWebApplication它们本质都是元注解式的Conditional。以ConditionalOnBean为例Target({ ElementType.TYPE, ElementType.METHOD }) Retention(RetentionPolicy.RUNTIME) Documented Conditional(OnBeanCondition.class) public interface ConditionalOnBean { Class?[] value() default {}; // 需要匹配的 bean 类型 String[] type() default {}; // 需要匹配的 bean 类型字符串形式 Class? extends Annotation[] annotation() default {}; // 匹配的 bean 注解 String[] name() default {}; // 需要匹配的 beanName SearchStrategy search() default SearchStrategy.ALL; // 搜索策略 Class?[] parameterizedContainer() default {}; // 泛型容器 }其中SearchStrategy枚举决定了 Bean 搜索范围public enum SearchStrategy { CURRENT, // 当前上下文 ANCESTORS, // 找所有的父容器 ALL // 当前上下文 父容器 }SpringBootCondition模板方法模式的骨架OnBeanCondition、OnClassCondition、OnWebApplicationCondition等条件类都继承自org.springframework.boot.autoconfigure.condition.SpringBootCondition它把Condition.matches固化成了模板方法Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName getClassOrMethodName(metadata); try { // 比较类,子类实现 ConditionOutcome outcome getMatchOutcome(context, metadata); // 日志输出 logOutcome(classOrMethodName, outcome); // 报告记录供 ConditionEvaluationReport / debug 使用 recordEvaluation(context, classOrMethodName, outcome); // 返回匹配结果 return outcome.isMatch(); } catch (NoClassDefFoundError ex) { /* 类缺失时的兜底处理 */ } catch (RuntimeException ex) { /* 统一异常包装 */ } }子类只需实现抽象方法getMatchOutcome(context, metadata)返回封装了match布尔值与ConditionMessage说明信息的ConditionOutcome。这样既统一了日志输出、评估报告记录等横切逻辑又保证了条件判断的可调试性Spring Boot 的ConditionEvaluationReport正是依赖recordEvaluation把每个自动配置类的命中/未命中原因记录在案供启动时--debug查看。自动装配阶段的三级过滤在 Spring Boot 启动阶段AutoConfigurationImportSelector#filter详见 SpringBoot-自动装配.md会从spring.factories中加载AutoConfigurationImportFilter实现org.springframework.boot.autoconfigure.AutoConfigurationImportFilter\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition这组过滤器会在候选自动配置类批量导入前先行筛掉不满足条件的类避免无意义的类加载随后真正注册每个配置类时框架层的ConditionEvaluator.shouldSkip仍会再次执行Conditional判断形成批量预过滤 逐个精细判断的两级防线。组合条件实战示例在条件类上还可以叠加Order控制判断顺序例如Component public class Beans { Bean public A a() { return new A(); } Bean ConditionalOnBean(value A.class) // 容器中存在 A 类型 Bean 才注册 B public B b() { return new B(); } }再如MessageSourceAutoConfiguration中同时使用ConditionalOnMissingBean(name messageSource, search SearchStrategy.CURRENT)、Conditional(ResourceBundleCondition.class)等多重条件组合充分展示了这套条件机制的灵活度。关联阅读Spring-Conditional.md本文原始笔记SpringBoot-ConditionalOnBean.mdSpring Boot 条件注解全剖析SpringBoot-自动装配.md自动配置类的候选、过滤与导入全流程Spring-BeanFactoryPostProcessor.mdBeanDefinition 注册后的定制扩展点Spring-scan.md组件扫描与 BeanDefinition 生成的另一条注册路径【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/4 22:50:48

AI SDK 如何校验 Provider 响应中的 URL 以防止 SSRF 攻击

AI SDK 如何校验 Provider 响应中的 URL 以防止 SSRF 攻击 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents 项目地址: https://gitcode.com…

2026/11/5 1:05:19

bd template 命令详解:用 Beads 模板系统统一 issue 创建规范

bd template 命令详解:用 Beads 模板系统统一 issue 创建规范 【免费下载链接】beads Beads - A memory upgrade for your coding agent 项目地址: https://gitcode.com/GitHub_Trending/beads1/beads Beads 的 bd template 命令体系用于管理 issue 模板&…

2026/11/5 10:43:56

工业测温方案解析:ADS122U04与STM32F042K6的UART高精度RTD采集设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 10:43:56

STM32L031+ADS122U04:UART接口24位ADC实现PT100高精度温度采集

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 10:43:55

基于ADS122U04与MK24的HVAC高精度温度采集系统设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 10:38:55

Univer开源Web Office引擎:Canvas渲染与协同编辑实战解析

第一次看到Univer这个项目名字的时候,我下意识以为是哪个海外团队用“University”做的教育类产品。直到我翻完它的仓库和在线Demo,才发现自己差点错过一个好东西——这是一个把“在线电子表格、文档、幻灯片”全部做成开源引擎的Web Office项目&#xf…

2026/11/5 10:38:55

Foxmail数字签名与加密配置全指南:S/MIME证书实战落地

1. 邮件签名与加密不是“加个水印”或“点个锁图标”那么简单很多人第一次在Foxmail里找“签名”功能,点开设置就以为万事大吉——填上姓名、电话、公司logo,保存退出,邮件发出去带个固定落款,就觉得“搞定”。等真遇到客户问“你…

2026/11/5 10:38:55

LVS负载均衡优化:FWM标记与持久化服务实践

1. 为什么需要优化LVS调度?在企业级负载均衡场景中,Linux Virtual Server(LVS)作为四层负载均衡的经典解决方案,其性能表现直接影响着整个业务系统的稳定性。但在实际生产环境中,我们常常会遇到两个典型问题…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑