资讯动态

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

发布时间:2026/9/13 20:12:53 来源:尧图企业网站定制
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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价