日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

面试:Spring Boot 中的条件注解底层是如何实现的?

發(fā)布時間:2025/3/21 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面试:Spring Boot 中的条件注解底层是如何实现的? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

SpringBoot內(nèi)部提供了特有的注解:條件注解(Conditional Annotation)。比如@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnExpression、@ConditionalOnMissingBean等。

條件注解存在的意義在于動態(tài)識別(也可以說是代碼自動化執(zhí)行)。比如@ConditionalOnClass會檢查類加載器中是否存在對應(yīng)的類,如果有的話被注解修飾的類就有資格被Spring容器所注冊,否則會被skip。

比如FreemarkerAutoConfiguration這個自動化配置類的定義如下:

@Configuration @ConditionalOnClass({?freemarker.template.Configuration.class,FreeMarkerConfigurationFactory.class?}) @AutoConfigureAfter(WebMvcAutoConfiguration.class) @EnableConfigurationProperties(FreeMarkerProperties.class) public?class?FreeMarkerAutoConfiguration

這個自動化配置類被@ConditionalOnClass條件注解修飾,這個條件注解存在的意義在于判斷類加載器中是否存在freemarker.template.Configuration和FreeMarkerConfigurationFactory這兩個類,如果都存在的話會在Spring容器中加載這個FreeMarkerAutoConfiguration配置類;否則不會加載。

條件注解內(nèi)部的一些基礎(chǔ)

在分析條件注解的底層實(shí)現(xiàn)之前,我們先來看一下這些條件注解的定義。以@ConditionalOnClass注解為例,它的定義如下:

@Target({?ElementType.TYPE,?ElementType.METHOD?}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public?@interface?ConditionalOnClass?{Class<?>[]?value()?default?{};?//?需要匹配的類String[]?name()?default?{};?//?需要匹配的類名 }

它有2個屬性,分別是類數(shù)組和字符串?dāng)?shù)組(作用一樣,類型不一樣),而且被@Conditional注解所修飾,這個@Conditional注解有個名為values的Class<? extends Condition>[]類型的屬性。這個Condition是個接口,用于匹配組件是否有資格被容器注冊,定義如下:

public?interface?Condition?{//?ConditionContext內(nèi)部會存儲Spring容器、應(yīng)用程序環(huán)境信息、資源加載器、類加載器boolean?matches(ConditionContext?context,?AnnotatedTypeMetadata?metadata); }

也就是說@Conditional注解屬性中可以持有多個Condition接口的實(shí)現(xiàn)類,所有的Condition接口需要全部匹配成功后這個@Conditional修飾的組件才有資格被注冊。

Condition接口有個子接口ConfigurationCondition:

public?interface?ConfigurationCondition?extends?Condition?{ConfigurationPhase?getConfigurationPhase();public?static?enum?ConfigurationPhase?{PARSE_CONFIGURATION,REGISTER_BEAN}}

這個子接口是一種特殊的條件接口,多了一個getConfigurationPhase方法,也就是條件注解的生效階段。只有在ConfigurationPhase中定義的兩種階段下才會生效。

Condition接口有個實(shí)現(xiàn)抽象類SpringBootCondition,SpringBoot中所有條件注解對應(yīng)的條件類都繼承這個抽象類。它實(shí)現(xiàn)了matches方法:

@Override public?final?boolean?matches(ConditionContext?context,AnnotatedTypeMetadata?metadata)?{String?classOrMethodName?=?getClassOrMethodName(metadata);?//?得到類名或者方法名(條件注解可以作用的類或者方法上)try?{ConditionOutcome?outcome?=?getMatchOutcome(context,?metadata);?//?抽象方法,具體子類實(shí)現(xiàn)。ConditionOutcome記錄了匹配結(jié)果boolean和log信息logOutcome(classOrMethodName,?outcome);?//?log記錄一下匹配信息recordEvaluation(context,?classOrMethodName,?outcome);?//?報(bào)告記錄一下匹配信息return?outcome.isMatch();?//?返回是否匹配}catch?(NoClassDefFoundError?ex)?{throw?new?IllegalStateException("Could?not?evaluate?condition?on?"?+?classOrMethodName?+?"?due?to?"+?ex.getMessage()?+?"?not?"+?"found.?Make?sure?your?own?configuration?does?not?rely?on?"+?"that?class.?This?can?also?happen?if?you?are?"+?"@ComponentScanning?a?springframework?package?(e.g.?if?you?"+?"put?a?@ComponentScan?in?the?default?package?by?mistake)",ex);}catch?(RuntimeException?ex)?{throw?new?IllegalStateException("Error?processing?condition?on?"?+?getName(metadata),?ex);} }

基于Class的條件注解

SpringBoot提供了兩個基于Class的條件注解:@ConditionalOnClass(類加載器中存在指明的類)或者@ConditionalOnMissingClass(類加載器中不存在指明的類)。

@ConditionalOnClass或者@ConditionalOnMissingClass注解對應(yīng)的條件類是OnClassCondition,定義如下:

@Order(Ordered.HIGHEST_PRECEDENCE)?//?優(yōu)先級、最高級別 class?OnClassCondition?extends?SpringBootCondition?{@Overridepublic?ConditionOutcome?getMatchOutcome(ConditionContext?context,AnnotatedTypeMetadata?metadata)?{StringBuffer?matchMessage?=?new?StringBuffer();?//?記錄匹配信息MultiValueMap<String,?Object>?onClasses?=?getAttributes(metadata,ConditionalOnClass.class);?//?得到@ConditionalOnClass注解的屬性if?(onClasses?!=?null)?{?//?如果屬性存在List<String>?missing?=?getMatchingClasses(onClasses,?MatchType.MISSING,context);?//?得到在類加載器中不存在的類if?(!missing.isEmpty())?{?//?如果存在類加載器中不存在對應(yīng)的類,返回一個匹配失敗的ConditionalOutcomereturn?ConditionOutcome.noMatch("required?@ConditionalOnClass?classes?not?found:?"+?StringUtils.collectionToCommaDelimitedString(missing));}//?如果類加載器中存在對應(yīng)的類的話,匹配信息進(jìn)行記錄matchMessage.append("@ConditionalOnClass?classes?found:?"+?StringUtils.collectionToCommaDelimitedString(getMatchingClasses(onClasses,?MatchType.PRESENT,?context)));}//?對@ConditionalOnMissingClass注解做相同的邏輯處理(說明@ConditionalOnClass和@ConditionalOnMissingClass可以一起使用)MultiValueMap<String,?Object>?onMissingClasses?=?getAttributes(metadata,ConditionalOnMissingClass.class);if?(onMissingClasses?!=?null)?{List<String>?present?=?getMatchingClasses(onMissingClasses,?MatchType.PRESENT,context);if?(!present.isEmpty())?{return?ConditionOutcome.noMatch("required?@ConditionalOnMissing?classes?found:?"+?StringUtils.collectionToCommaDelimitedString(present));}matchMessage.append(matchMessage.length()?==?0???""?:?"?");matchMessage.append("@ConditionalOnMissing?classes?not?found:?"+?StringUtils.collectionToCommaDelimitedString(getMatchingClasses(onMissingClasses,?MatchType.MISSING,?context)));}//?返回全部匹配成功的ConditionalOutcomereturn?ConditionOutcome.match(matchMessage.toString());}private?enum?MatchType?{?//?枚舉:匹配類型。用于查詢類名在對應(yīng)的類加載器中是否存在。PRESENT?{?//?匹配成功@Overridepublic?boolean?matches(String?className,?ConditionContext?context)?{return?ClassUtils.isPresent(className,?context.getClassLoader());}},MISSING?{?//?匹配不成功@Overridepublic?boolean?matches(String?className,?ConditionContext?context)?{return?!ClassUtils.isPresent(className,?context.getClassLoader());}};public?abstract?boolean?matches(String?className,?ConditionContext?context);}}

比如FreemarkerAutoConfiguration中的@ConditionalOnClass注解中有value屬性是freemarker.template.Configuration.class和FreeMarkerConfigurationFactory.class。在OnClassCondition執(zhí)行過程中得到的最終ConditionalOutcome中的log message如下:

@ConditionalOnClass?classes?found:?freemarker.template.Configuration,org.springframework.ui.freemarker.FreeMarkerConfigurationFactory

基于Bean的條件注解

@ConditionalOnBean(Spring容器中存在指明的bean)、@ConditionalOnMissingBean(Spring容器中不存在指明的bean)以及ConditionalOnSingleCandidate(Spring容器中存在且只存在一個指明的bean)都是基于Bean的條件注解,它們對應(yīng)的條件類是ConditionOnBean。

@ConditionOnBean注解定義如下:

@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?{};?//?匹配的bean的名字SearchStrategy?search()?default?SearchStrategy.ALL;?//?搜索策略。提供CURRENT(只在當(dāng)前容器中找)、PARENTS(只在所有的父容器中找;但是不包括當(dāng)前容器)和ALL(CURRENT和PARENTS的組合) }

OnBeanCondition條件類的匹配代碼如下:

@Override public?ConditionOutcome?getMatchOutcome(ConditionContext?context,AnnotatedTypeMetadata?metadata)?{StringBuffer?matchMessage?=?new?StringBuffer();?//?記錄匹配信息if?(metadata.isAnnotated(ConditionalOnBean.class.getName()))?{BeanSearchSpec?spec?=?new?BeanSearchSpec(context,?metadata,ConditionalOnBean.class);?//?構(gòu)造一個BeanSearchSpec,會從@ConditionalOnBean注解中獲取屬性,然后設(shè)置到BeanSearchSpec中List<String>?matching?=?getMatchingBeans(context,?spec);?//?從BeanFactory中根據(jù)策略找出所有匹配的beanif?(matching.isEmpty())?{?//?如果沒有匹配的bean,返回一個沒有匹配成功的ConditionalOutcomereturn?ConditionOutcome.noMatch("@ConditionalOnBean?"?+?spec?+?"?found?no?beans");}//?如果找到匹配的bean,匹配信息進(jìn)行記錄matchMessage.append("@ConditionalOnBean?"?+?spec?+?"?found?the?following?"?+?matching);}if?(metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName()))?{?//?相同的邏輯,針對@ConditionalOnSingleCandidate注解BeanSearchSpec?spec?=?new?SingleCandidateBeanSearchSpec(context,?metadata,ConditionalOnSingleCandidate.class);List<String>?matching?=?getMatchingBeans(context,?spec);if?(matching.isEmpty())?{return?ConditionOutcome.noMatch("@ConditionalOnSingleCandidate?"?+?spec?+?"?found?no?beans");}else?if?(!hasSingleAutowireCandidate(context.getBeanFactory(),?matching))?{?//?多了一層判斷,判斷是否只有一個beanreturn?ConditionOutcome.noMatch("@ConditionalOnSingleCandidate?"?+?spec+?"?found?no?primary?candidate?amongst?the"?+?"?following?"+?matching);}matchMessage.append("@ConditionalOnSingleCandidate?"?+?spec?+?"?found?"+?"a?primary?candidate?amongst?the?following?"?+?matching);}if?(metadata.isAnnotated(ConditionalOnMissingBean.class.getName()))?{?//?相同的邏輯,針對@ConditionalOnMissingBean注解BeanSearchSpec?spec?=?new?BeanSearchSpec(context,?metadata,ConditionalOnMissingBean.class);List<String>?matching?=?getMatchingBeans(context,?spec);if?(!matching.isEmpty())?{return?ConditionOutcome.noMatch("@ConditionalOnMissingBean?"?+?spec+?"?found?the?following?"?+?matching);}matchMessage.append(matchMessage.length()?==?0???""?:?"?");matchMessage.append("@ConditionalOnMissingBean?"?+?spec?+?"?found?no?beans");}return?ConditionOutcome.match(matchMessage.toString());?//返回匹配成功的ConditonalOutcome }

SpringBoot還提供了其他比如ConditionalOnJava、ConditionalOnNotWebApplication、ConditionalOnWebApplication、ConditionalOnResource、ConditionalOnProperty、ConditionalOnExpression等條件注解,有興趣的讀者可以自行查看它們的底層處理邏輯。

各種條件注解的總結(jié)

條件注解對應(yīng)的Condition處理類處理邏輯
@ConditionalOnBeanOnBeanConditionSpring容器中是否存在對應(yīng)的實(shí)例。可以通過實(shí)例的類型、類名、注解、昵稱去容器中查找(可以配置從當(dāng)前容器中查找或者父容器中查找或者兩者一起查找)這些屬性都是數(shù)組,通過”與”的關(guān)系進(jìn)行查找
@ConditionalOnClassOnClassCondition類加載器中是否存在對應(yīng)的類。可以通過Class指定(value屬性)或者Class的全名指定(name屬性)。如果是多個類或者多個類名的話,關(guān)系是”與”關(guān)系,也就是說這些類或者類名都必須同時在類加載器中存在
@ConditionalOnExpressionOnExpressionCondition判斷SpEL 表達(dá)式是否成立
@ConditionalOnJavaOnJavaCondition指定Java版本是否符合要求。內(nèi)部有2個屬性value和range。value表示一個枚舉的Java版本,range表示比這個老或者新于等于指定的Java版本(默認(rèn)是新于等于)。內(nèi)部會基于某些jdk版本特有的類去類加載器中查詢,比如如果是jdk9,類加載器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,類加載器中需要存在java.util.function.Function;如果是jdk7,類加載器中需要存在java.nio.file.Files;如果是jdk6,類加載器中需要存在java.util.ServiceLoader
@ConditionalOnMissingBeanOnBeanConditionSpring容器中是否缺少對應(yīng)的實(shí)例。可以通過實(shí)例的類型、類名、注解、昵稱去容器中查找(可以配置從當(dāng)前容器中查找或者父容器中查找或者兩者一起查找)這些屬性都是數(shù)組,通過”與”的關(guān)系進(jìn)行查找。還多了2個屬性ignored(類名)和ignoredType(類名),匹配的過程中會忽略這些bean
@ConditionalOnMissingClassOnClassCondition跟ConditionalOnClass的處理邏輯一樣,只是條件相反,在類加載器中不存在對應(yīng)的類
@ConditionalOnNotWebApplicationOnWebApplicationCondition應(yīng)用程序是否是非Web程序,沒有提供屬性,只是一個標(biāo)識。會從判斷Web程序特有的類是否存在,環(huán)境是否是Servlet環(huán)境,容器是否是Web容器等
@ConditionalOnPropertyOnPropertyCondition應(yīng)用環(huán)境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing屬性。prefix表示屬性名的前綴,name是屬性名,havingValue是具體的屬性值,matchIfMissing是個boolean值,如果屬性不存在,這個matchIfMissing為true的話,會繼續(xù)驗(yàn)證下去,否則屬性不存在的話直接就相當(dāng)于匹配不成功
@ConditionalOnResourceOnResourceCondition是否存在指定的資源文件。只有一個屬性resources,是個String數(shù)組。會從類加載器中去查詢對應(yīng)的資源文件是否存在
@ConditionalOnSingleCandidateOnBeanConditionSpring容器中是否存在且只存在一個對應(yīng)的實(shí)例。只有3個屬性value、type、search。跟ConditionalOnBean中的這3種屬性值意義一樣
@ConditionalOnWebApplicationOnWebApplicationCondition應(yīng)用程序是否是Web程序,沒有提供屬性,只是一個標(biāo)識。會從判斷Web程序特有的類是否存在,環(huán)境是否是Servlet環(huán)境,容器是否是Web容器等
例子例子意義
@ConditionalOnBean(javax.sql.DataSource.class)Spring容器或者所有父容器中需要存在至少一個javax.sql.DataSource類的實(shí)例
@ConditionalOnClass ({ Configuration.class, FreeMarkerConfigurationFactory.class })類加載器中必須存在Configuration和FreeMarkerConfigurationFactory這兩個類
@ConditionalOnExpression (“‘${server.host}’==’localhost’”)server.host配置項(xiàng)的值需要是localhost
ConditionalOnJava(JavaVersion.EIGHT)Java版本至少是8
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)Spring當(dāng)前容器中不存在ErrorController類型的bean
@ConditionalOnMissingClass (“GenericObjectPool”)類加載器中不能存在GenericObjectPool這個類
@ConditionalOnNotWebApplication必須在非Web應(yīng)用下才會生效
@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true)應(yīng)用程序的環(huán)境中必須有spring.aop.auto這項(xiàng)配置,且它的值是true或者環(huán)境中不存在spring.aop.auto配置(matchIfMissing為true)
@ConditionalOnResource (resources=”mybatis.xml”)類加載路徑中必須存在mybatis.xml文件
@ConditionalOnSingleCandidate (PlatformTransactionManager.class)Spring當(dāng)前或父容器中必須存在PlatformTransactionManager這個類型的實(shí)例,且只有一個實(shí)例
@ConditionalOnWebApplication必須在Web應(yīng)用下才會生效

SpringBoot條件注解的激活機(jī)制

分析完了條件注解的執(zhí)行邏輯之后,接下來的問題就是SpringBoot是如何讓這些條件注解生效的?

SpringBoot使用ConditionEvaluator這個內(nèi)部類完成條件注解的解析和判斷。

在Spring容器的refresh過程中,只有跟解析或者注冊bean有關(guān)系的類都會使用ConditionEvaluator完成條件注解的判斷,這個過程中一些類不滿足條件的話就會被skip。這些類比如有AnnotatedBeanDefinitionReader、ConfigurationClassBeanDefinitionReader、ConfigurationClassParse、ClassPathScanningCandidateComponentProvider等。

比如ConfigurationClassParser的構(gòu)造函數(shù)會初始化內(nèi)部屬性conditionEvaluator:

public?ConfigurationClassParser(MetadataReaderFactory?metadataReaderFactory,ProblemReporter?problemReporter,?Environment?environment,?ResourceLoader?resourceLoader,BeanNameGenerator?componentScanBeanNameGenerator,?BeanDefinitionRegistry?registry)?{this.metadataReaderFactory?=?metadataReaderFactory;this.problemReporter?=?problemReporter;this.environment?=?environment;this.resourceLoader?=?resourceLoader;this.registry?=?registry;this.componentScanParser?=?new?ComponentScanAnnotationParser(resourceLoader,?environment,?componentScanBeanNameGenerator,?registry);//?構(gòu)造ConditionEvaluator用于處理?xiàng)l件注解this.conditionEvaluator?=?new?ConditionEvaluator(registry,?environment,?resourceLoader); }

ConfigurationClassParser對每個配置類進(jìn)行解析的時候都會使用ConditionEvaluator:

if?(this.conditionEvaluator.shouldSkip(configClass.getMetadata(),?ConfigurationPhase.PARSE_CONFIGURATION))?{return; }

ConditionEvaluator的skip方法:

public?boolean?shouldSkip(AnnotatedTypeMetadata?metadata,?ConfigurationPhase?phase)?{//?如果這個類沒有被@Conditional注解所修飾,不會skipif?(metadata?==?null?||?!metadata.isAnnotated(Conditional.class.getName()))?{return?false;}//?如果參數(shù)中沒有設(shè)置條件注解的生效階段if?(phase?==?null)?{//?是配置類的話直接使用PARSE_CONFIGURATION階段if?(metadata?instanceof?AnnotationMetadata?&&ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata)?metadata))?{return?shouldSkip(metadata,?ConfigurationPhase.PARSE_CONFIGURATION);}//?否則使用REGISTER_BEAN階段return?shouldSkip(metadata,?ConfigurationPhase.REGISTER_BEAN);}//?要解析的配置類的條件集合List<Condition>?conditions?=?new?ArrayList<Condition>();//?獲取配置類的條件注解得到條件數(shù)據(jù),并添加到集合中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();}//?沒有這個解析類不需要階段的判斷或者解析類和參數(shù)中的階段一致才會繼續(xù)進(jìn)行if?(requiredPhase?==?null?||?requiredPhase?==?phase)?{//?階段一致切不滿足條件的話,返回true并跳過這個bean的解析if?(!condition.matches(this.context,?metadata))?{return?true;}}}return?false; }

SpringBoot在條件注解的解析log記錄在了ConditionEvaluationReport類中,可以通過BeanFactory獲取(BeanFactory是有父子關(guān)系的;每個BeanFactory都存有一份ConditionEvaluationReport,互不相干):

ConditionEvaluationReport?conditionEvaluationReport?=?beanFactory.getBean("autoConfigurationReport",?ConditionEvaluationReport.class); Map<String,?ConditionEvaluationReport.ConditionAndOutcomes>?result?=?conditionEvaluationReport.getConditionAndOutcomesBySource(); for(String?key?:?result.keySet())?{ConditionEvaluationReport.ConditionAndOutcomes?conditionAndOutcomes?=?result.get(key);Iterator<ConditionEvaluationReport.ConditionAndOutcome>?iterator?=?conditionAndOutcomes.iterator();while(iterator.hasNext())?{ConditionEvaluationReport.ConditionAndOutcome?conditionAndOutcome?=?iterator.next();System.out.println(key?+?"?--?"?+?conditionAndOutcome.getCondition().getClass().getSimpleName()?+?"?--?"?+?conditionAndOutcome.getOutcome());} }

打印出條件注解下的類加載信息:

....... org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration?--?OnClassCondition?--?required?@ConditionalOnClass?classes?not?found:?freemarker.template.Configuration,org.springframework.ui.freemarker.FreeMarkerConfigurationFactory org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration?--?OnClassCondition?--?required?@ConditionalOnClass?classes?not?found:?groovy.text.markup.MarkupTemplateEngine org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration?--?OnClassCondition?--?required?@ConditionalOnClass?classes?not?found:?com.google.gson.Gson org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration?--?OnClassCondition?--?required?@ConditionalOnClass?classes?not?found:?org.h2.server.web.WebServlet org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration?--?OnClassCondition?--?required?@ConditionalOnClass?classes?not?found:?org.springframework.hateoas.Resource,org.springframework.plugin.core.Plugin org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration?--?OnClassCondition?--?required?@ConditionalOnClass?classes?not?found:?com.hazelcast.core.HazelcastInstance .......

一些測試的例子代碼在 https://github.com/fangjian0423/springboot-analysis/tree/master/springboot-conditional 上

總結(jié)

以上是生活随笔為你收集整理的面试:Spring Boot 中的条件注解底层是如何实现的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。