來源 :http://fangjian0423.github.io/2017/05/16/springboot-condition-annotation/
SpringBoot內部提供了特有的註解:條件註解(Conditional Annotation)。比如:
- @ConditionalOnBean、
- @ConditionalOnClass、
- @ConditionalOnExpression、
- @ConditionalOnMissingBean等。
條件註解存在的意義在於動態識別(也可以說是代碼自動化執行)。比如@ConditionalOnClass會檢查類加載器中是否存在對應的類,如果有的話被註解修飾的類就有資格被Spring容器所註冊,否則會被skip。
比如FreemarkerAutoConfiguration這個自動化配置類的定義如下:
<code>@Configuration
@ConditionalOnClass
({ freemarker.template.Configuration.class, FreeMarkerConfigurationFactory.class })@AutoConfigureAfter
(WebMvcAutoConfiguration.class)@EnableConfigurationProperties
(FreeMarkerProperties.class) public class FreeMarkerAutoConfiguration/<code>
這個自動化配置類被@ConditionalOnClass條件註解修飾,這個條件註解存在的意義在於判斷類加載器中是否存在freemarker.template.Configuration和FreeMarkerConfigurationFactory這兩個類,如果都存在的話會在Spring容器中加載這個FreeMarkerAutoConfiguration配置類;否則不會加載。
條件註解內部的一些基礎
在分析條件註解的底層實現之前,我們先來看一下這些條件註解的定義。以@ConditionalOnClass註解為例,它的定義如下:
<code>public
ConditionalOnClass { Class>[] value()default
{}; String[] name()default
{}; }/<code>
它有2個屬性,分別是類數組和字符串數組(作用一樣,類型不一樣),而且被@Conditional註解所修飾,這個@Conditional註解有個名為values的Class extends Condition>[]類型的屬性。這個Condition是個接口,用於匹配組件是否有資格被容器註冊,定義如下:
<code>public
interface
Condition
{boolean
matches
(ConditionContext context, AnnotatedTypeMetadata metadata)
; }/<code>
也就是說@Conditional註解屬性中可以持有多個Condition接口的實現類,所有的Condition接口需要全部匹配成功後這個@Conditional修飾的組件才有資格被註冊。
Condition接口有個子接口ConfigurationCondition:
<code>public
interface
ConfigurationCondition
extends
Condition
{ConfigurationPhase
getConfigurationPhase
()
;public
static
enum
ConfigurationPhase { PARSE_CONFIGURATION, REGISTER_BEAN } }/<code>
這個子接口是一種特殊的條件接口,多了一個getConfigurationPhase方法,也就是條件註解的生效階段。只有在ConfigurationPhase中定義的兩種階段下才會生效。
Condition接口有個實現抽象類SpringBootCondition,SpringBoot中所有條件註解對應的條件類都繼承這個抽象類。它實現了matches方法:
基於Class的條件註解
SpringBoot提供了兩個基於Class的條件註解:@ConditionalOnClass(類加載器中存在指明的類)或者@ConditionalOnMissingClass(類加載器中不存在指明的類)。
@ConditionalOnClass或者@ConditionalOnMissingClass註解對應的條件類是OnClassCondition,定義如下:
比如FreemarkerAutoConfiguration中的@ConditionalOnClass註解中有value屬性是freemarker.template.Configuration.class和FreeMarkerConfigurationFactory.class。在OnClassCondition執行過程中得到的最終ConditionalOutcome中的log message如下:
<code>1@ConditionalOnClass
classesfound:
freemarker.template.Configuration,org.springframework.ui.freemarker.FreeMarkerConfigurationFactory/<code>
基於Bean的條件註解
@ConditionalOnBean(Spring容器中存在指明的bean)、@ConditionalOnMissingBean(Spring容器中不存在指明的bean)以及ConditionalOnSingleCandidate(Spring容器中存在且只存在一個指明的bean)都是基於Bean的條件註解,它們對應的條件類是ConditionOnBean。
@ConditionOnBean註解定義如下:
<code>@Target
({ ElementType.TYPE
, ElementType.METHOD
})@Retention
(RetentionPolicy.RUNTIME)@Documented
@Conditional
(OnBeanCondition.class) public@interface
ConditionalOnBean { Class>[]
value() default {}; String[]
type() default {}; Class extends Annotation>[]
annotation() default {}; String[]
name() default {}; SearchStrategy search() default SearchStrategy.ALL
; }/<code>
OnBeanCondition條件類的匹配代碼如下:
<code>public
ConditionOutcomegetMatchOutcome
(ConditionContext context, AnnotatedTypeMetadata metadata)
{ StringBuffer matchMessage =new
StringBuffer();if
(metadata.isAnnotated(ConditionalOnBean.
class
.getName
())) { BeanSearchSpec spec =new
BeanSearchSpec(context, metadata, ConditionalOnBean.
class
); List matching = getMatchingBeans(context, spec);if
(matching.isEmpty()) {return
ConditionOutcome .noMatch("@ConditionalOnBean "
+ spec +" found no beans"
); } matchMessage.append("@ConditionalOnBean "
+ spec +" found the following "
+ matching); }if
(metadata.isAnnotated(ConditionalOnSingleCandidate.
class
.getName
())) { BeanSearchSpec spec =new
SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.
class
); List matching = getMatchingBeans(context, spec);if
(matching.isEmpty()) {return
ConditionOutcome.noMatch("@ConditionalOnSingleCandidate "
+ spec +" found no beans"
); }else
if
(!hasSingleAutowireCandidate(context.getBeanFactory(), matching)) {return
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
())) { BeanSearchSpec spec =new
BeanSearchSpec(context, metadata, ConditionalOnMissingBean.
class
); List 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()); }/<code>
SpringBoot還提供了其他比如ConditionalOnJava、ConditionalOnNotWebApplication、ConditionalOnWebApplication、ConditionalOnResource、ConditionalOnProperty、ConditionalOnExpression等條件註解,有興趣的讀者可以自行查看它們的底層處理邏輯。
各種條件註解的總結
SpringBoot條件註解的激活機制
分析完了條件註解的執行邏輯之後,接下來的問題就是SpringBoot是如何讓這些條件註解生效的?
SpringBoot使用ConditionEvaluator這個內部類完成條件註解的解析和判斷。
在Spring容器的refresh過程中,只有跟解析或者註冊bean有關係的類都會使用ConditionEvaluator完成條件註解的判斷,這個過程中一些類不滿足條件的話就會被skip。這些類比如有AnnotatedBeanDefinitionReader、ConfigurationClassBeanDefinitionReader、ConfigurationClassParse、ClassPathScanningCandidateComponentProvider等。
比如ConfigurationClassParser的構造函數會初始化內部屬性conditionEvaluator:
<code>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);this
.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); } ConfigurationClassParser對每個配置類進行解析的時候都會使用ConditionEvaluator:if
(this
.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return
; }/<code>
ConditionEvaluator的skip方法:
<code>public
boolean shouldSkip(AnnotatedTypeMetadata metadata, 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); } List conditions = new ArrayList();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) {if
(!condition.matches(this
.context, metadata)) {return
true
; } } }return
false
; }/<code>
SpringBoot在條件註解的解析log記錄在了ConditionEvaluationReport類中,可以通過BeanFactory獲取(BeanFactory是有父子關係的;每個BeanFactory都存有一份ConditionEvaluationReport,互不相干):
<code>ConditionEvaluationReport conditionEvaluationReport = beanFactory.getBean("autoConfigurationReport"
, ConditionEvaluationReport.
class
); Map result = conditionEvaluationReport.getConditionAndOutcomesBySource();for
(String key : result.keySet()) { ConditionEvaluationReport.ConditionAndOutcomes conditionAndOutcomes = result.get
(key); Iterator iterator = conditionAndOutcomes.iterator();while
(iterator.hasNext()) { ConditionEvaluationReport.ConditionAndOutcome conditionAndOutcome = iterator.next(); System.out
.println(key +" -- "
+ conditionAndOutcome.getCondition().getClass().getSimpleName() +" -- "
+ conditionAndOutcome.getOutcome()); } }/<code>
打印出條件註解下的類加載信息:
<code>.......org
.springframework
.boot
.autoconfigure
.freemarker
.FreeMarkerAutoConfiguration
--
OnClassCondition
--
required
@ConditionalOnClass
classesnot
found:
freemarker.template.Configuration,org.springframework.ui.freemarker.FreeMarkerConfigurationFactory org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classesnot
found:
groovy.text.markup.MarkupTemplateEngine org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classesnot
found:
com.google.gson.Gson org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classesnot
found:
org.h2.server.web.WebServlet org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classesnot
found:
org.springframework.hateoas.Resource,org.springframework.plugin.core.Plugin org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classesnot
found:
com.hazelcast.core.HazelcastInstance ......./<code>