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

发布时间:2026/9/13 20:13:05

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/9/13 20:13:05

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/9/13 20:13:05

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/9/13 20:58:08

UART协议详解:从异步通信原理到串口实战排错

UART的全称是Universal Asynchronous Receiver/Transmitter,中文叫通用异步收发器,它对应的通信方式,就是嵌入式领域最常见的那种异步串行通信。我当年对串口的第一印象很朴素:把一根杜邦线从单片机TXD接到另一块板的RXD&#xff…

2026/9/13 20:58:08

一维振动信号转二维图像的故障诊断方法

简介:本资源是一套面向工业智能诊断领域的Matlab实现工具包,专为高校研究者、自动化工程师及深度学习初学者设计,解决一维传感器时序信号难以直接输入主流图像型深度学习模型的关键问题。核心方法为暂态提取变换(TET)&…

2026/9/13 20:58:08

Python语音处理:用librosa提取MFCC特征完整指南

简介:面向音频处理与机器学习入门者的MFCC特征提取示例代码包,使用Python语言和librosa库实现,可直接运行并生成直观的梅尔频率倒谱图。程序能够读取wav格式音频,计算梅尔频率倒谱系数,并将结果以谱图形式呈现&#xf…

2026/9/13 20:58:08

2025嵌入式面试高频考点全解析:C语言、Linux与RTOS核心追问

这几年带过不少候选人,也帮好几拨师弟师妹突击过嵌入式开发岗位的面试,一个很直观的感受是:2025年的嵌入式面试,和三五年前完全不是一个打法了。面试官不再满足于让你背几个C语言修饰符的解释,而是直接把一块开发板、一…

2026/9/13 20:58:08

BIM 可视化技术在房地产和工程领域的应用

BIM(建筑信息模型)技术正在深刻改变建筑行业,而 BIM 可视化是 BIM 技术最直观、最有价值的应用方向之一。通过将 BIM 模型转化为逼真的三维可视化内容,可以服务于设计沟通、施工管理、营销展示、运营维护等全生命周期。本文全面解…

2026/9/13 20:53:07

Matlab在售电公司购售电策略优化中的应用

1. 项目背景与核心价值在能源结构转型的大背景下,售电公司作为连接发电侧与用户侧的关键纽带,其购售电策略直接影响着运营效益和市场稳定性。传统策略往往将可再生能源输出视为固定值,而实际上光伏、风电等清洁能源存在显著的预测误差&#x…

2026/9/13 0:01:16

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/13 0:01:16

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/13 11:18:28

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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