
Spring Conditional 注解源碼深度解析從 ConditionEvaluator 到條件化 Bean 注冊【免費下載鏈接】source-code-hunter 從源碼層面剖析挖掘互聯(lián)網(wǎng)行業(yè)主流技術的底層實現(xiàn)原理為廣大開發(fā)者 “提升技術深度” 提供便利。目前開放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中間件等項目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter導讀本文基于當前倉庫 docs/Spring/clazz/Spring-Conditional.md 的源碼閱讀筆記深入剖析 Spring 框架Conditional條件注解的底層實現(xiàn)原理。Conditional是 Spring 條件化配置的基石也是 Spring Boot 自動裝配ConditionalOnClass、ConditionalOnBean等賴以運轉的核心機制。讀完本文你將掌握Conditional的注解定義、Condition匹配器的執(zhí)行流程、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是一個函數(shù)式接口FunctionalInterface只有一個核心方法matchesFunctionalInterface public interface Condition { /** * 匹配,如果匹配返回true進行初始化,返回false跳過初始化 */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }方法簽名中的兩個參數(shù)是條件判斷的全部信息來源ConditionContext context條件上下文封裝了 BeanDefinition 注冊表、BeanFactory、Environment 環(huán)境、資源加載器、類加載器等容器運行時信息AnnotatedTypeMetadata metadata注解元數(shù)據(jù)描述了當前被Conditional標注的類或方法上的注解信息。只要matches返回falseSpring 就會跳過對應配置類或 Bean 的初始化返回true則正常注冊。兩個關鍵參數(shù)ConditionContext 與 AnnotatedTypeMetadataConditionContext條件判斷的運行環(huán)境public interface ConditionContext { /** * bean的定義 */ BeanDefinitionRegistry getRegistry(); /** * bean 工廠 */ Nullable ConfigurableListableBeanFactory getBeanFactory(); /** * 環(huán)境 */ Environment getEnvironment(); /** * 資源加載器 */ ResourceLoader getResourceLoader(); /** * 類加載器 */ Nullable ClassLoader getClassLoader(); }五個方法分別暴露了容器中五個維度的資源方法返回類型用途getRegistry()BeanDefinitionRegistry讀取/注冊 BeanDefinition判斷某個 Bean 是否已定義getBeanFactory()ConfigurableListableBeanFactory操作 BeanFactory查詢 BeanDefinition 等getEnvironment()Environment讀取系統(tǒng)屬性、環(huán)境變量、配置文件中的屬性getResourceLoader()ResourceLoader加載資源配合ConditionalOnResource等場景getClassLoader()ClassLoader判斷某個類是否存在于 classpath配合ConditionalOnClass場景唯一實現(xiàn)是內(nèi)部類org.springframework.context.annotation.ConditionEvaluator.ConditionContextImpl。其構造方法會在創(chuàng)建時根據(jù)傳入?yún)?shù)推導出完整的上下文信息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共同推導。也就是說即使調(diào)用方只傳入一個BeanDefinitionRegistry容器也能把其余四項環(huán)境信息補齊。AnnotatedTypeMetadata注解元數(shù)據(jù)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); } }這是一個元數(shù)據(jù)接口Spring 通過它對外暴露被評估的類/方法上標注了哪些注解、注解屬性是什么。MergedAnnotations是 Spring 5.2 引入的合并注解視圖能統(tǒng)一處理注解的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類時執(zhí)行條件判斷此時Bean方法尚未處理適用于決定某個配置類整體是否加載REGISTER_BEANBean 注冊階段在配置類解析完成后、Bean 注冊時執(zhí)行適用于決定某個Bean方法產(chǎn)生的 Bean 是否注冊。當調(diào)用方?jīng)]有顯式傳入phase時Spring 會根據(jù)metadata的類型自動推導若metadata是AnnotationMetadata且ConfigurationClassUtils.isConfigurationCandidate判定其為配置類候選即標注了Configuration或Component系列注解則按PARSE_CONFIGURATION階段執(zhí)行否則按REGISTER_BEAN階段執(zhí)行。第三階段加載并排序所有 ConditionSpring 通過getConditionClasses(metadata)讀取Conditional注解的value屬性多個條件類然后用getCondition實例化每個條件類該過程會合并元注解即支持通過元注解間接標注Conditional。實例化后的條件列表會用AnnotationAwareOrderComparator.sort(conditions)排序——這意味著條件類可以通過實現(xiàn)Ordered接口或標注Order注解來控制判斷先后順序這一點在 Spring Boot 的組合條件場景中非常重要。第四階段逐條執(zhí)行 matches遍歷排序后的條件列表對每個Condition調(diào)用matches若條件實現(xiàn)了ConfigurationCondition則取出其聲明的requiredPhase只有當requiredPhase為null即普通Condition任何階段都參與或與當前phase相等時才執(zhí)行matches一旦某個條件matches返回falseshouldSkip立即返回true跳過注冊所有條件都通過才返回false不跳過。也就是說Conditional的多個條件之間是AND關系——任意一個不滿足整個配置即被跳過。調(diào)用鏈條件判斷發(fā)生在 Bean 注冊的最前面ConditionEvaluator.shouldSkip的調(diào)用點位于org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean——這是注冊 Bean 時執(zhí)行的第一個方法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); // 和條件注解相關的函數(shù) if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } // 省略其他 }流程非常清晰doRegisterBean先把目標類包裝成AnnotatedGenericBeanDefinition緊接著調(diào)用shouldSkip(abd.getMetadata())此時phase為null由 Spring 自動推導。若返回true則直接return后續(xù)的 BeanDefinition 注冊、依賴注入統(tǒng)統(tǒng)不再執(zhí)行只有通過條件判斷的 Bean 才會繼續(xù)走完注冊流程。可以推斷shouldSkip的調(diào)用點不限于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 狀態(tài)層面驗證了實例化BeanTwoConfiguration時Spring 會去執(zhí)行NoBeanOneCondition.matches方法返回false即整體跳過。實戰(zhàn)自定義 Condition 實現(xiàn)條件化配置基于上面的源碼機制自定義一個條件配置只需三步實現(xiàn)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提供的五類資源按環(huán)境屬性context.getEnvironment().getProperty(xxx)按 Bean 是否存在context.getBeanFactory().containsBeanDefinition(beanName)按類是否在 classpathcontext.getClassLoader().loadClass(com.xxx.Yyy)按資源是否存在context.getResourceLoader().getResource(classpath:xxx.xml)。如需控制條件執(zhí)行階段可讓條件類實現(xiàn)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 { // 比較類,子類實現(xiàn) ConditionOutcome outcome getMatchOutcome(context, metadata); // 日志輸出 logOutcome(classOrMethodName, outcome); // 報告記錄供 ConditionEvaluationReport / debug 使用 recordEvaluation(context, classOrMethodName, outcome); // 返回匹配結果 return outcome.isMatch(); } catch (NoClassDefFoundError ex) { /* 類缺失時的兜底處理 */ } catch (RuntimeException ex) { /* 統(tǒng)一異常包裝 */ } }子類只需實現(xiàn)抽象方法getMatchOutcome(context, metadata)返回封裝了match布爾值與ConditionMessage說明信息的ConditionOutcome。這樣既統(tǒng)一了日志輸出、評估報告記錄等橫切邏輯又保證了條件判斷的可調(diào)試性Spring Boot 的ConditionEvaluationReport正是依賴recordEvaluation把每個自動配置類的命中/未命中原因記錄在案供啟動時--debug查看。自動裝配階段的三級過濾在 Spring Boot 啟動階段AutoConfigurationImportSelector#filter詳見 SpringBoot-自動裝配.md會從spring.factories中加載AutoConfigurationImportFilter實現(xiàn)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仍會再次執(zhí)行Conditional判斷形成批量預過濾 逐個精細判斷的兩級防線。組合條件實戰(zhàn)示例在條件類上還可以疊加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)等多重條件組合充分展示了這套條件機制的靈活度。關聯(lián)閱讀Spring-Conditional.md本文原始筆記SpringBoot-ConditionalOnBean.mdSpring Boot 條件注解全剖析SpringBoot-自動裝配.md自動配置類的候選、過濾與導入全流程Spring-BeanFactoryPostProcessor.mdBeanDefinition 注冊后的定制擴展點Spring-scan.md組件掃描與 BeanDefinition 生成的另一條注冊路徑【免費下載鏈接】source-code-hunter 從源碼層面剖析挖掘互聯(lián)網(wǎng)行業(yè)主流技術的底層實現(xiàn)原理為廣大開發(fā)者 “提升技術深度” 提供便利。目前開放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中間件等項目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考