盘点SpringConditional
一。前言
这一篇来看一下Conditional的使用和原理,先来看一下整体的体系结构
二。使用ConditionalOnBean:当容器里有指定Bean的条件下。ConditionalOnMissingBean:当容器里没有指定Bean的情况下。ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选BeanConditionalOnClass:当类路径下有指定类的条件下。ConditionalOnMissingClass:当类路径下没有指定类的条件下。ConditionalOnProperty:指定的属性是否有指定的值ConditionalOnResource:类路径是否有指定的值ConditionalOnExpression:基于SpEL表达式作为判断条件。ConditionalOnJava:基于Java版本作为判断条件ConditionalOnJndi:在JNDI存在的条件下差在指定的位置ConditionalOnNotWebApplication:当前项目不是Web项目的条件下ConditionalOnWebApplication:当前项目是Web项目的条件下2。1基础使用2。2自定义ConditionalpublicclassDefaultConditionalimplementsCondition{privateLoggerloggerLoggerFactory。getLogger(this。getClass());Overridepublicbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){logger。info(进行Conditional判断);returnfalse;}}2。3基础使用BeanConditionalOnBean(TestService。class)publicConfigBeangetConfigBean(){logger。info(开始加载ConditionalOnBean);returnnewConfigBean();}三。自定义原理分析3。1Conditional入口
对于Configuration。Bean的创建方式,Conditinal的起点是refushinvokeBeanFactoryPostProcessors,在其中会调用ConfigurationClassPostProcessor进行处理privatevoidloadBeanDefinitionsForBeanMethod(BeanMethodbeanMethod){Step1:获取当前Configuration中Bean的元数据信息ConfigurationClassconfigClassbeanMethod。getConfigurationClass();MethodMetadatametadatabeanMethod。getMetadata();StringmethodNamemetadata。getMethodName();判断是否应该跳过当前Beanif(this。conditionEvaluator。shouldSkip(metadata,ConfigurationPhase。REGISTERBEAN)){configClass。skippedBeanMethods。add(methodName);return;}if(configClass。skippedBeanMethods。contains(methodName)){return;}}3。2Conditional判断的流程
ConditionEvaluator是核心处理类,最终都会调用shouldSkip判断是否跳过CConditionEvaluatorpublicbooleanshouldSkip(NullableAnnotatedTypeMetadatametadata,NullableConfigurationPhasephase){Step1:如果当前Bean不包含Conditional,则直接返回if(metadatanull!metadata。isAnnotated(Conditional。class。getName())){returnfalse;}Step2:有2种ConfigurationPhase的类型,表示2种配置的阶段PARSECONFIGURATION:Condition应该在解析Configuration类时进行计算,如果此时条件不匹配,则不会添加Configuration类REGISTERBEAN:条件不会阻止Configuration类被添加,在评估条件时,所有Configuration类都将被解析if(phasenull){if(metadatainstanceofAnnotationMetadataConfigurationClassUtils。isConfigurationCandidate((AnnotationMetadata)metadata)){returnshouldSkip(metadata,ConfigurationPhase。PARSECONFIGURATION);}returnshouldSkip(metadata,ConfigurationPhase。REGISTERBEAN);}Step3:获取所有的Condition对象ListConditionconditionsnewArrayList();for(String〔〕conditionClasses:getConditionClasses(metadata)){for(StringconditionClass:conditionClasses){ConditionconditiongetCondition(conditionClass,this。context。getClassLoader());conditions。add(condition);}}Step4:排序AnnotationAwareOrderComparator。sort(conditions);for(Conditioncondition:conditions){ConfigurationPhaserequiredPhasenull;if(conditioninstanceofConfigurationCondition){requiredPhase((ConfigurationCondition)condition)。getConfigurationPhase();}Step5:最终的Condition匹配过程if((requiredPhasenullrequiredPhasephase)!condition。matches(this。context,metadata)){returntrue;}}returnfalse;}
直到这里就开始匹配到对应的方法四。常规加载方式
解析
已知的Conditional是基于SpringBootCondition实现的,其具体抽象类为FilteringSpringBootCondition,看一下主要的继承关系
去除不需要配置的类
第一步是快速去除不需要的类,主要流程如下:起点:AbstractApplicationContextrefreshinvokeBeanFactoryPostProcessors处理:ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry处理主要逻辑拦截:ConfigurationClassFilterfilter匹配:FilteringSpringBootConditionmatch
注意,这里是进行match匹配,目的是获取基于正在导入的Configuration类的AnnotationMetadata的AutoConfigurationEntry4。1Filter拦截
Filter拦截是在ConfigurationClassFilter,其中会对所有的Conditional进行拦截处理privatestaticclassConfigurationClassFilter{自动配置的元数据privatefinalAutoConfigurationMetadataautoConfigurationMetadata;ListStringfilter(ListStringconfigurations){longstartTimeSystem。nanoTime();此处为所有的Confiturations类String〔〕candidatesStringUtils。toStringArray(configurations);booleanskippedfalse;此处包含OnBeanCondition,OnClassCondition,OnWebApplicationCondition三种for(AutoConfigurationImportFilterfilter:this。filters){获取是否存在match匹配boolean〔〕matchfilter。match(candidates,this。autoConfigurationMetadata);for(inti0;imatch。length;i){if(!match〔i〕){candidates〔i〕null;skippedtrue;}}}if(!skipped){returnconfigurations;}如果不能跳过,记录当前Confiturations类ListStringresultnewArrayList(candidates。length);for(Stringcandidate:candidates){if(candidate!null){result。add(candidate);}}returnresult;}}4。2FilteringSpringBootCondition中match匹配
此处是重写了其父类的match方法publicboolean〔〕match(String〔〕autoConfigurationClasses,AutoConfigurationMetadataautoConfigurationMetadata){Step1:准备Report对象,用于记录ConditionEvaluationReportreportConditionEvaluationReport。find(this。beanFactory);Step2:获取对应的所有的Condition方法ConditionOutcome〔〕outcomesgetOutcomes(autoConfigurationClasses,autoConfigurationMetadata);boolean〔〕matchnewboolean〔outcomes。length〕;for(inti0;ioutcomes。length;i){对match中的数组进行赋值,当outcomes对应下标的ConditionOutcome匹配时为true。其他情况,返回falsematch〔i〕(outcomes〔i〕nulloutcomes〔i〕。isMatch());if(!match〔i〕outcomes〔i〕!null){记录日志logOutcome(autoConfigurationClasses〔i〕,outcomes〔i〕);if(report!null){像ConditionEvaluationReportSortedMap存放评估条件report。recordConditionEvaluation(autoConfigurationClasses〔i〕,this,outcomes〔i〕);}}}returnmatch;}
ConditionEvaluationReport的作用
该对象用来记录自动化配置过程中条件匹配的详细信息及日志信息publicfinalclassConditionEvaluationReport{bean名称privatestaticfinalStringBEANNAMEautoConfigurationReport;创建一个父的条件匹配对象privatestaticfinalAncestorsMatchedConditionANCESTORCONDITIONnewAncestorsMatchedCondition();存放类名或方法名(key),条件评估输出对象(value)privatefinalSortedMapString,ConditionAndOutcomesoutcomesnewTreeMap();是否是原始条件匹配对象privatebooleanaddedAncestorOutcomes;父的条件评估报告对象privateConditionEvaluationReportparent;记录已经从条件评估中排除的类名称privatefinalListStringexclusionsnewArrayList();记录作为条件评估的候选类名称privatefinalSetStringunconditionalClassesnewHashSet();}4。3getOutcomes获取
此处以OnBean为例,此处存在一定的关联关系:protectedfinalConditionOutcome〔〕getOutcomes(String〔〕autoConfigurationClasses,AutoConfigurationMetadataautoConfigurationMetadata){Step1:初始化一个和处理类等容量的数组ConditionOutcome〔〕outcomesnewConditionOutcome〔autoConfigurationClasses。length〕;Step2:遍历所有的autoConfigurationClassesfor(inti0;ioutcomes。length;i){StringautoConfigurationClassautoConfigurationClasses〔i〕;if(autoConfigurationClass!null){SetStringonBeanTypesautoConfigurationMetadata。getSet(autoConfigurationClass,ConditionalOnBean);判断是否存在ConditionalOnBean标注的方法outcomes〔i〕getOutcome(onBeanTypes,ConditionalOnBean。class);判断是否需要输出ConditionOutcomeif(outcomes〔i〕null){SetStringonSingleCandidateTypesautoConfigurationMetadata。getSet(autoConfigurationClass,ConditionalOnSingleCandidate);此处是返回是否要处理outcomes〔i〕getOutcome(onSingleCandidateTypes,ConditionalOnSingleCandidate。class);}}}returnoutcomes;}4。4如何判断是否符合评估条件
注意,这里的matches和FilteringSpringBootCondition不是一个FilteringSpringBootConditionmatch:基于AutoConfigurationImportFilter,对给定的自动配置类候选应用筛选器SpringBootConditionmatches:需要返回最终的判断结果
调用流程在loadBeanDefinitionsForBeanMethod等类似流程种调用shouldSkip,从而跳转到该逻辑Overridepublicfinalbooleanmatches(ConditionContextcontext,AnnotatedTypeMetadatametadata){获取注解对应的方法名或者类名StringclassOrMethodNamegetClassOrMethodName(metadata);try{获取对应的条件匹配类,此处会判断metadatainstanceof,有限判断是否为ClassMetadataConditionOutcomeoutcomegetMatchOutcome(context,metadata);很简单的打印日志,Trace级别logOutcome(classOrMethodName,outcome);记录结果,通过ConditionEvaluationReport和recordEvaluation方法实现recordEvaluation(context,classOrMethodName,outcome);返回是否成功匹配returnoutcome。isMatch();}catch(NoClassDefFoundErrorex){thrownewIllegalStateException(。。。。。。);}catch(RuntimeExceptionex){thrownewIllegalStateException(ErrorprocessingconditionongetName(metadata),ex);}}
这里的核心就是调用getMatchOutcome判断是否符合或者不符合要求,getMatchOutcome需要子类重写五。getMatchOutcome详情5。1OnClasspublicConditionOutcomegetMatchOutcome(ConditionContextcontext,AnnotatedTypeMetadatametadata){Step1:获取当前容器ClassLoaderClassLoaderclassLoadercontext。getClassLoader();ConditionMessagematchMessageConditionMessage。empty();Step2:判断是否有ConditionalOnClass约束ListStringonClassesgetCandidates(metadata,ConditionalOnClass。class);if(onClasses!null){Step21:filter过滤,判断是否缺失类ListStringmissingfilter(onClasses,ClassNameFilter。MISSING,classLoader);if(!missing。isEmpty()){returnConditionOutcome。noMatch(ConditionMessage。forCondition(ConditionalOnClass。class)。didNotFind(requiredclass,requiredclasses)。items(Style。QUOTE,missing));}Step22:构建matchMessagematchMessagematchMessage。andCondition(ConditionalOnClass。class)。found(requiredclass,requiredclasses)。items(Style。QUOTE,filter(onClasses,ClassNameFilter。PRESENT,classLoader));}Step3:同理,判断是否需要MissClassesListStringonMissingClassesgetCandidates(metadata,ConditionalOnMissingClass。class);if(onMissingClasses!null){。。。。与ConditionalOnClass基本类似,此处省略}returnConditionOutcome。match(matchMessage);}
补充:ConditionMessage的作用5。2OnBean
与OnBean类似,这里就展示一种publicConditionOutcomegetMatchOutcome(ConditionContextcontext,AnnotatedTypeMetadatametadata){ConditionMessagematchMessageConditionMessage。empty();MergedAnnotationsannotationsmetadata。getAnnotations();if(annotations。isPresent(ConditionalOnBean。class)){Step1:获取ConditionalOnBean注解信息SpecConditionalOnBeanspecnewSpec(context,metadata,annotations,ConditionalOnBean。class);Step2:返回匹配结果其内部通过getNamesOfBeansIgnoredByType,getBeanNamesForType等方式判断类是否存在MatchResultmatchResultgetMatchingBeans(context,spec);if(!matchResult。isAllMatched()){StringreasoncreateOnBeanNoMatchReason(matchResult);returnConditionOutcome。noMatch(spec。message()。because(reason));}matchMessagespec。message(matchMessage)。found(bean,beans)。items(Style。QUOTE,matchResult。getNamesOfAllMatches());}if(metadata。isAnnotated(ConditionalOnSingleCandidate。class。getName())){。。。。。}if(metadata。isAnnotated(ConditionalOnMissingBean。class。getName())){。。。。。}returnConditionOutcome。match(matchMessage);}总结
Conditional本身并不难,这一篇主要是为了完善图谱以及后续的starter启动流程方案做准备。
整个流程中有几个环节理解就行了:Spring中的Conditional都会继承SpringBootCondition,会实现其getOutcomes方法getOutcomes是用于快速去掉无需加载的Configuration,getMatchOutcome是为了验证匹配关系通常都会通过ConditionEvaluator的shouldSkip判断是否需要跳过Bean流程