《Spring Boot 源碼學(xué)習(xí)系列》
引言
前面的博文,Huazie 帶大家從 Spring Boot
源碼深入了解了自動配置類的讀取和篩選的過程,然后又詳解了OnClassCondition、 OnBeanCondition、OnWebApplicationCondition 這三個自動配置過濾匹配子類實現(xiàn)。
在上述的博文中,我們其實已經(jīng)初步涉及到了像 @ConditionalOnClass
、@ConditionalOnBean
、@ConditionalOnWebApplication
這樣的條件注解,并且這些條件注解里面,我們都能看到 @Conditional
注解。
往期內(nèi)容
在開始本篇的內(nèi)容介紹之前,我們先來看看往期的系列文章【有需要的朋友,歡迎關(guān)注系列專欄】:
Spring Boot 源碼學(xué)習(xí) |
Spring Boot 項目介紹 |
Spring Boot 核心運行原理介紹 |
【Spring Boot 源碼學(xué)習(xí)】@EnableAutoConfiguration 注解 |
【Spring Boot 源碼學(xué)習(xí)】@SpringBootApplication 注解 |
【Spring Boot 源碼學(xué)習(xí)】走近 AutoConfigurationImportSelector |
【Spring Boot 源碼學(xué)習(xí)】自動裝配流程源碼解析(上) |
【Spring Boot 源碼學(xué)習(xí)】自動裝配流程源碼解析(下) |
【Spring Boot 源碼學(xué)習(xí)】深入 FilteringSpringBootCondition |
【Spring Boot 源碼學(xué)習(xí)】OnClassCondition 詳解 |
【Spring Boot 源碼學(xué)習(xí)】OnBeanCondition 詳解 |
【Spring Boot 源碼學(xué)習(xí)】OnWebApplicationCondition 詳解 |
主要內(nèi)容
本篇我們重點介紹 @Conditional
條件注解,參見如下:
1. 初識 @Conditional
我們先來看看 @Conditional
注解的源碼【Spring Context 5.3.25】:
/**
* 表示組件僅在所有指定條件匹配時才有資格注冊。
*
* 條件是在bean定義即將注冊之前可以通過編程確定的任何狀態(tài)(有關(guān)詳細(xì)信息,請參閱Condition)。
*
* @Conditional注解可以以以下任意方式使用:
* 作為類型級別的注釋直接或間接地應(yīng)用于帶有@Component的任何類,包括@Configuration類
* 作為元注釋,用于組合自定義注釋標(biāo)簽
* 作為@Bean方法上的注釋級別注解
*
* 如果一個@Configuration類被標(biāo)記為@Conditional,則該類的所有@Bean方法、@Import注解和@ComponentScan注解都將受到條件約束。
*
* @author Phillip Webb
* @author Sam Brannen
* @since 4.0
* @see Condition
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 必須匹配才能注冊組件的所有條件類
*/
Class<? extends Condition>[] value();
}
翻看上述源碼,可以看到 @Conditional
條件注解是從 Spring 4.0 開始引入的,它表示組件僅在所有指定條件匹配時才有資格注冊。比如,當(dāng)類加載器下存在某個指定的類的時候才會對注解的類進行實例化操作。
它唯一的元素屬性是接口 Condition
的數(shù)組,只有數(shù)組中指定的所有 Condition
的 matches
方法都返回 true
的情況下,被注解的類才會被加載。我們前面講到的 OnClassCondition
等類就是 Condition
的子類之一。
/**
* 一個必須匹配才能注冊的單個 Condition。
*
* <p> 在 bean 定義即將被注冊之前立即進行檢查,并可以根據(jù)在該點可以確定的任何標(biāo)準(zhǔn)自由否決注冊。
*
* <p> 條件必須遵循與 BeanFactoryPostProcessor 相同的限制,并確保不要與 bean 實例進行交互。
* 對于與 @Configuration beans交互的更細(xì)粒度的控制,請考慮實現(xiàn) ConfigurationCondition 接口。
*
* @author Phillip Webb
* @since 4.0
* @see ConfigurationCondition
* @see Conditional
* @see ConditionContext
*/
@FunctionalInterface
public interface Condition {
/**
* 確定條件是否匹配。
* @param context 條件上下文
* @param metadata 正在檢查的 AnnotationMetadata 或 MethodMetadata 的元數(shù)據(jù)
* @return 如果條件匹配并且可以注冊組件,則返回 true;否則返回 false,否決帶有注解的組件的注冊。
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
上述就是 Condition
接口的源碼,它的 matches
方法用來確定條件是否匹配,其中兩個參數(shù)分別如下:
-
ConditionContext
:條件上下文,可通過該接口提供的方法來獲得 Spring 應(yīng)用的上下文信息,接口定義如下:public interface ConditionContext { /** * 返回一個 BeanDefinitionRegistry 對象,該對象將包含如果條件匹配時應(yīng)該持有的bean定義。 * 如果沒有可用的注冊表(這種情況很少見:只有當(dāng)使用 ClassPathScanningCandidateComponentProvider 時才會出現(xiàn)), * 則會拋出IllegalStateException異常。 */ BeanDefinitionRegistry getRegistry(); /** * 返回一個 ConfigurableListableBeanFactory 對象,該對象將包含如果條件匹配時應(yīng)該持有的bean定義, * 或者 如果bean工廠不可用(或者無法向下轉(zhuǎn)型為 ConfigurableListableBeanFactory),則返回null。 */ @Nullable ConfigurableListableBeanFactory getBeanFactory(); /** * 返回當(dāng)前應(yīng)用程序正在運行的環(huán)境。 */ Environment getEnvironment(); /** * 返回當(dāng)前正在使用的資源加載器。 */ ResourceLoader getResourceLoader(); /** * 返回應(yīng)該用來加載額外類的 ClassLoader。如果系統(tǒng)類加載器不可訪問,則返回null。 */ @Nullable ClassLoader getClassLoader(); }
-
AnnotatedTypeMetadata
:該接口提供了訪問特定類或方法的注解功能,并且不需要加載類,可以用來檢查帶有@Bean
注解的方法上是否還有其他注解。下面我們來查看下它的源碼【spring-core 5.3.25】:
public interface AnnotatedTypeMetadata { // 返回一個MergedAnnotations對象,表示該類型的注解集合。 MergedAnnotations getAnnotations(); // 檢查是否存在指定名稱的注解,如果存在則返回true,否則返回false。 default boolean isAnnotated(String annotationName) { return this.getAnnotations().isPresent(annotationName); } // 下面的方法,都是用來獲取指定名稱注解的屬性值 @Nullable default Map<String, Object> getAnnotationAttributes(String annotationName) { return this.getAnnotationAttributes(annotationName, false); } @Nullable default Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) { MergedAnnotation<Annotation> annotation = this.getAnnotations().get(annotationName, (Predicate)null, MergedAnnotationSelectors.firstDirectlyDeclared()); return !annotation.isPresent() ? null : annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, true)); } @Nullable default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) { return this.getAllAnnotationAttributes(annotationName, false); } @Nullable default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { Adapt[] adaptations = Adapt.values(classValuesAsString, true); return (MultiValueMap)this.getAnnotations().stream(annotationName).filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)).map(MergedAnnotation::withNonMergedAttributes).collect(MergedAnnotationCollectors.toMultiValueMap((map) -> { return map.isEmpty() ? null : map; }, adaptations)); } }
2. @Conditional 的衍生注解
在 Spring Boot 的 autoconfigure 項目中提供了各類基于@Conditional
注解的衍生注解,它們均位于 spring-boot-autoconfigure 項目的 org.springframework.boot.autoconfigure.condition
包下,如下圖所示:
上述有好幾個條件注解,我們已經(jīng)接觸過了,下面我們再仔細(xì)介紹一下:
-
@ConditionalOnBean
:當(dāng)容器中有指定 Bean 的條件下。 -
@ConditionalOnClass
:當(dāng) classpath 類路徑下有指定類的條件下。 -
@ConditionalOnCloudPlatform
:當(dāng)指定的云平臺處于 active 狀態(tài)時。 -
@ConditionalOnExpression
:基于 SpEL 表達(dá)式的條件判斷。 -
@ConditionalOnJava
:基于 JVM 版本作為判斷條件。 -
@ConditionalOnJndi
:在 JNDI 存在的條件下查找指定的位置。 -
@ConditionalOnMissingBean
:當(dāng)容器里沒有指定 Bean 的條件。 -
@ConditionalOnMissingClass
:當(dāng)類路徑下沒有指定類的條件下。 -
@ConditionalOnNotWebApplication
:當(dāng)項目不是一個 Web 項目的條件下。 -
@ConditionalOnProperty
:當(dāng)指定的屬性有指定的值的條件下。 -
@ConditionalOnResource
:類路徑是否有指定的值。 -
@ConditionalOnSingleCandidate
:當(dāng)指定的 Bean 在容器中只有一個,或者有多個但是指定了首選的 Bean。 -
@ConditionalOnWarDeployment
:當(dāng)應(yīng)用以 War 包形式部署時(例如在 Tomcat、Jetty 等 Web 服務(wù)器中) -
@ConditionalOnWebApplication
:當(dāng)項目是一個 Web 項目的條件下
如果我們仔細(xì)觀察這些注解的源碼,很快會發(fā)現(xiàn)它們其實都組合了@Conditional
注解,不同的是它們在注解中指定的條件(Condition
)不同。
下面我們以前面博文中了解過的 @ConditionalOnWebApplication
為例,來對衍生條件注解進行一個簡單的分析:
/**
* 用于條件性地匹配應(yīng)用程序是否為Web應(yīng)用程序。默認(rèn)情況下,任何Web應(yīng)用程序都會匹配,但可以通過type()屬性進行縮小范圍。
*
* @author Dave Syer
* @author Stephane Nicoll
* @since 1.0.0
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
// 所需的web應(yīng)用類型
Type type() default Type.ANY;
// 可選應(yīng)用類型枚舉
enum Type {
// 任何類型
ANY,
// 基于servlet的web應(yīng)用
SERVLET,
// 基于reactive的web應(yīng)用
REACTIVE
}
}
通過查看 @ConditionalOnWebApplication
注解的源碼,我們發(fā)現(xiàn)它的確組合了 @Conditional
注解,并且指定了對應(yīng)的 Condition 為OnWebApplicationCondition
。該類繼承自 SpringBootCondition
并實現(xiàn) AutoConfigurationImportFilter
接口。
有關(guān) OnWebApplicationCondition
類的詳細(xì)介紹,請查看筆者的《【Spring Boot 源碼學(xué)習(xí)】OnWebApplicationCondition 詳解》,
了解了條件類的相關(guān)內(nèi)容后,我們可以用如下圖來表示 Condition
接口相關(guān)功能及實現(xiàn)類:
總結(jié)
本篇我們介紹 @Conditional
條件注解及其衍生注解,至此有關(guān)自動配置裝配的流程已經(jīng)基本介紹完畢。文章來源:http://www.zghlxwxcb.cn/news/detail-734421.html
雖然我們從源碼角度對自動裝配流程有了清晰的認(rèn)識,但還是不能熟練地運用。那么下篇博文,我們將以 Spring Boot 內(nèi)置的 http
編碼功能為例來分析一下整個自動配置的過程。文章來源地址http://www.zghlxwxcb.cn/news/detail-734421.html
到了這里,關(guān)于【Spring Boot 源碼學(xué)習(xí)】@Conditional 條件注解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!