javascript
面试:Spring Boot 中的条件注解底层是如何实现的?
SpringBoot內部提供了特有的注解:條件注解(Conditional Annotation)。比如@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnExpression、@ConditionalOnMissingBean等。
條件注解存在的意義在于動態識別(也可以說是代碼自動化執行)。比如@ConditionalOnClass會檢查類加載器中是否存在對應的類,如果有的話被注解修飾的類就有資格被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配置類;否則不會加載。
條件注解內部的一些基礎
在分析條件注解的底層實現之前,我們先來看一下這些條件注解的定義。以@ConditionalOnClass注解為例,它的定義如下:
@Target({?ElementType.TYPE,?ElementType.METHOD?}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public?@interface?ConditionalOnClass?{Class<?>[]?value()?default?{};?//?需要匹配的類String[]?name()?default?{};?//?需要匹配的類名 }它有2個屬性,分別是類數組和字符串數組(作用一樣,類型不一樣),而且被@Conditional注解所修飾,這個@Conditional注解有個名為values的Class<? extends Condition>[]類型的屬性。這個Condition是個接口,用于匹配組件是否有資格被容器注冊,定義如下:
public?interface?Condition?{//?ConditionContext內部會存儲Spring容器、應用程序環境信息、資源加載器、類加載器boolean?matches(ConditionContext?context,?AnnotatedTypeMetadata?metadata); }也就是說@Conditional注解屬性中可以持有多個Condition接口的實現類,所有的Condition接口需要全部匹配成功后這個@Conditional修飾的組件才有資格被注冊。
Condition接口有個子接口ConfigurationCondition:
public?interface?ConfigurationCondition?extends?Condition?{ConfigurationPhase?getConfigurationPhase();public?static?enum?ConfigurationPhase?{PARSE_CONFIGURATION,REGISTER_BEAN}}這個子接口是一種特殊的條件接口,多了一個getConfigurationPhase方法,也就是條件注解的生效階段。只有在ConfigurationPhase中定義的兩種階段下才會生效。
Condition接口有個實現抽象類SpringBootCondition,SpringBoot中所有條件注解對應的條件類都繼承這個抽象類。它實現了matches方法:
@Override public?final?boolean?matches(ConditionContext?context,AnnotatedTypeMetadata?metadata)?{String?classOrMethodName?=?getClassOrMethodName(metadata);?//?得到類名或者方法名(條件注解可以作用的類或者方法上)try?{ConditionOutcome?outcome?=?getMatchOutcome(context,?metadata);?//?抽象方法,具體子類實現。ConditionOutcome記錄了匹配結果boolean和log信息logOutcome(classOrMethodName,?outcome);?//?log記錄一下匹配信息recordEvaluation(context,?classOrMethodName,?outcome);?//?報告記錄一下匹配信息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注解對應的條件類是OnClassCondition,定義如下:
@Order(Ordered.HIGHEST_PRECEDENCE)?//?優先級、最高級別 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())?{?//?如果存在類加載器中不存在對應的類,返回一個匹配失敗的ConditionalOutcomereturn?ConditionOutcome.noMatch("required?@ConditionalOnClass?classes?not?found:?"+?StringUtils.collectionToCommaDelimitedString(missing));}//?如果類加載器中存在對應的類的話,匹配信息進行記錄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?{?//?枚舉:匹配類型。用于查詢類名在對應的類加載器中是否存在。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執行過程中得到的最終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的條件注解,它們對應的條件類是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(只在當前容器中找)、PARENTS(只在所有的父容器中找;但是不包括當前容器)和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);?//?構造一個BeanSearchSpec,會從@ConditionalOnBean注解中獲取屬性,然后設置到BeanSearchSpec中List<String>?matching?=?getMatchingBeans(context,?spec);?//?從BeanFactory中根據策略找出所有匹配的beanif?(matching.isEmpty())?{?//?如果沒有匹配的bean,返回一個沒有匹配成功的ConditionalOutcomereturn?ConditionOutcome.noMatch("@ConditionalOnBean?"?+?spec?+?"?found?no?beans");}//?如果找到匹配的bean,匹配信息進行記錄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等條件注解,有興趣的讀者可以自行查看它們的底層處理邏輯。
各種條件注解的總結
| @ConditionalOnBean | OnBeanCondition | Spring容器中是否存在對應的實例。可以通過實例的類型、類名、注解、昵稱去容器中查找(可以配置從當前容器中查找或者父容器中查找或者兩者一起查找)這些屬性都是數組,通過”與”的關系進行查找 |
| @ConditionalOnClass | OnClassCondition | 類加載器中是否存在對應的類??梢酝ㄟ^Class指定(value屬性)或者Class的全名指定(name屬性)。如果是多個類或者多個類名的話,關系是”與”關系,也就是說這些類或者類名都必須同時在類加載器中存在 |
| @ConditionalOnExpression | OnExpressionCondition | 判斷SpEL 表達式是否成立 |
| @ConditionalOnJava | OnJavaCondition | 指定Java版本是否符合要求。內部有2個屬性value和range。value表示一個枚舉的Java版本,range表示比這個老或者新于等于指定的Java版本(默認是新于等于)。內部會基于某些jdk版本特有的類去類加載器中查詢,比如如果是jdk9,類加載器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,類加載器中需要存在java.util.function.Function;如果是jdk7,類加載器中需要存在java.nio.file.Files;如果是jdk6,類加載器中需要存在java.util.ServiceLoader |
| @ConditionalOnMissingBean | OnBeanCondition | Spring容器中是否缺少對應的實例??梢酝ㄟ^實例的類型、類名、注解、昵稱去容器中查找(可以配置從當前容器中查找或者父容器中查找或者兩者一起查找)這些屬性都是數組,通過”與”的關系進行查找。還多了2個屬性ignored(類名)和ignoredType(類名),匹配的過程中會忽略這些bean |
| @ConditionalOnMissingClass | OnClassCondition | 跟ConditionalOnClass的處理邏輯一樣,只是條件相反,在類加載器中不存在對應的類 |
| @ConditionalOnNotWebApplication | OnWebApplicationCondition | 應用程序是否是非Web程序,沒有提供屬性,只是一個標識。會從判斷Web程序特有的類是否存在,環境是否是Servlet環境,容器是否是Web容器等 |
| @ConditionalOnProperty | OnPropertyCondition | 應用環境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing屬性。prefix表示屬性名的前綴,name是屬性名,havingValue是具體的屬性值,matchIfMissing是個boolean值,如果屬性不存在,這個matchIfMissing為true的話,會繼續驗證下去,否則屬性不存在的話直接就相當于匹配不成功 |
| @ConditionalOnResource | OnResourceCondition | 是否存在指定的資源文件。只有一個屬性resources,是個String數組。會從類加載器中去查詢對應的資源文件是否存在 |
| @ConditionalOnSingleCandidate | OnBeanCondition | Spring容器中是否存在且只存在一個對應的實例。只有3個屬性value、type、search。跟ConditionalOnBean中的這3種屬性值意義一樣 |
| @ConditionalOnWebApplication | OnWebApplicationCondition | 應用程序是否是Web程序,沒有提供屬性,只是一個標識。會從判斷Web程序特有的類是否存在,環境是否是Servlet環境,容器是否是Web容器等 |
| @ConditionalOnBean(javax.sql.DataSource.class) | Spring容器或者所有父容器中需要存在至少一個javax.sql.DataSource類的實例 |
| @ConditionalOnClass ({ Configuration.class, FreeMarkerConfigurationFactory.class }) | 類加載器中必須存在Configuration和FreeMarkerConfigurationFactory這兩個類 |
| @ConditionalOnExpression (“‘${server.host}’==’localhost’”) | server.host配置項的值需要是localhost |
| ConditionalOnJava(JavaVersion.EIGHT) | Java版本至少是8 |
| @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) | Spring當前容器中不存在ErrorController類型的bean |
| @ConditionalOnMissingClass (“GenericObjectPool”) | 類加載器中不能存在GenericObjectPool這個類 |
| @ConditionalOnNotWebApplication | 必須在非Web應用下才會生效 |
| @ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) | 應用程序的環境中必須有spring.aop.auto這項配置,且它的值是true或者環境中不存在spring.aop.auto配置(matchIfMissing為true) |
| @ConditionalOnResource (resources=”mybatis.xml”) | 類加載路徑中必須存在mybatis.xml文件 |
| @ConditionalOnSingleCandidate (PlatformTransactionManager.class) | Spring當前或父容器中必須存在PlatformTransactionManager這個類型的實例,且只有一個實例 |
| @ConditionalOnWebApplication | 必須在Web應用下才會生效 |
SpringBoot條件注解的激活機制
分析完了條件注解的執行邏輯之后,接下來的問題就是SpringBoot是如何讓這些條件注解生效的?
SpringBoot使用ConditionEvaluator這個內部類完成條件注解的解析和判斷。
在Spring容器的refresh過程中,只有跟解析或者注冊bean有關系的類都會使用ConditionEvaluator完成條件注解的判斷,這個過程中一些類不滿足條件的話就會被skip。這些類比如有AnnotatedBeanDefinitionReader、ConfigurationClassBeanDefinitionReader、ConfigurationClassParse、ClassPathScanningCandidateComponentProvider等。
比如ConfigurationClassParser的構造函數會初始化內部屬性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);//?構造ConditionEvaluator用于處理條件注解this.conditionEvaluator?=?new?ConditionEvaluator(registry,?environment,?resourceLoader); }ConfigurationClassParser對每個配置類進行解析的時候都會使用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;}//?如果參數中沒有設置條件注解的生效階段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>();//?獲取配置類的條件注解得到條件數據,并添加到集合中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();}//?沒有這個解析類不需要階段的判斷或者解析類和參數中的階段一致才會繼續進行if?(requiredPhase?==?null?||?requiredPhase?==?phase)?{//?階段一致切不滿足條件的話,返回true并跳過這個bean的解析if?(!condition.matches(this.context,?metadata))?{return?true;}}}return?false; }SpringBoot在條件注解的解析log記錄在了ConditionEvaluationReport類中,可以通過BeanFactory獲取(BeanFactory是有父子關系的;每個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 上
總結
以上是生活随笔為你收集整理的面试:Spring Boot 中的条件注解底层是如何实现的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Logback 配置文件这么写,TPS
- 下一篇: 程序员如何精确评估开发时间?