SpringBoot運作原理之@Conditional
在《SpringBoot運作原理解析之載入AutoConfiguration》中我們已經介紹了SpringBoot對配置檔案的載入及相應類的例項化操作。那麼,SpringBoot是如何之後該例項化哪些類的呢?這篇文章帶大家瞭解一下@Conditional 註解及其發揮的作用。
@Conditional 註解
@Conditional 註解可以根據是否滿足某一個特定條件來決定要不要建立某個特定的Bean。比如,當某一個jar包在一個類路徑下的時自動配置一個或多個Bean;或者只有某個Bean被建立才會建立另外一個Bean。該註解由Spring4開始提供,原始碼如下:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class<? extends Condition>[] value(); }
SpringBoot也正是使用@Conditional 的這項功能來實現自動配置的。SpringBoot對該註解進行了相應個擴充套件,形成了以下組合註解,以滿足更多的情況。
- @ConditionalOnBean :當容器中有指定Bean的條件下。
- @ConditionalOnClass :當classpath類路徑下有指定類的條件下。
- @ConditionalOnCloudPlatform :當指定的雲平臺處於active狀態時。
- @ConditionalOnExpression :基於SpEL表示式的條件判斷。
- @ConditionalOnJava :基於JVM版本作為判斷條件。
- @ConditionalOnJndi :在JNDI存在的條件下查詢指定的位置。
- @ConditionalOnMissingBean :當容器裡沒有指定Bean的條件。
- @ConditionalOnMissingClass :當類路徑下沒有指定類的條件下。
- @ConditionalOnNotWebApplication :當專案不是一個Web專案的條件下。
- @ConditionalOnProperty :當指定的屬性有指定的值的條件下。
- @ConditionalOnResource :類路徑是否有指定的值。
- @ConditionalOnSingleCandidate :當指定的Bean在容器中只有一個,或者有多個但是指定了首選的Bean。
- @ConditionalOnWebApplication :當專案是一個Web專案的條件下。
以上組合註解均位於spring-boot-autoconfigure jar包下的org.springframework.boot.autoconfigure.condition包下。
@ConditionalOnJava 說明
瞭解組合註解,現在以一個簡單的註解@ConditionalOnJava 來說明一下組合註解的簡單實用。@ConditionalOnJava 的原始碼為:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnJavaCondition.class) public @interface ConditionalOnJava { Range range() default Range.EQUAL_OR_NEWER; JavaVersion value(); enum Range { EQUAL_OR_NEWER, OLDER_THAN } }
很明顯,它是由@Conditional 註解組合而成。在@Conditional 中需要滿足OnJavaCondition.class定義的條件。OnJavaCondition類程式碼如下:
@Order(Ordered.HIGHEST_PRECEDENCE + 20) class OnJavaCondition extends SpringBootCondition { private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion(); @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { Map<String, Object> attributes = metadata .getAnnotationAttributes(ConditionalOnJava.class.getName()); Range range = (Range) attributes.get("range"); JavaVersion version = (JavaVersion) attributes.get("value"); return getMatchOutcome(range, JVM_VERSION, version); } protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) { boolean match = isWithin(runningVersion, range, version); String expected = String.format( (range != Range.EQUAL_OR_NEWER) ? "(older than %s)" : "(%s or newer)", version); ConditionMessage message = ConditionMessage .forCondition(ConditionalOnJava.class, expected) .foundExactly(runningVersion); return new ConditionOutcome(match, message); } private boolean isWithin(JavaVersion runningVersion, Range range, JavaVersion version) { if (range == Range.EQUAL_OR_NEWER) { return runningVersion.isEqualOrNewerThan(version); } if (range == Range.OLDER_THAN) { return runningVersion.isOlderThan(version); } throw new IllegalStateException("Unknown range " + range); } }
通過原始碼可以看出,OnJavaCondition繼承了SpringBootCondition類,並實現了它的getMatchOutcome方法。該方法的實現主要做了以下事情:
- 獲取當前使用jdk版本。
- 獲取註解屬性中range(判斷範圍)和value(jdk版本)。
- 通過isWithin方法比較當前版本是否在指定的範圍內。
- 返回比對結果。
使用機制
同樣在spring-boot-autoconfigure jar包下的org.springframework.boot.autoconfigure包下,springboot預設提供了一些自動配置類。隨便開啟一個AutoConfiguration類都會看到使用到上面的註解:
@Configuration @EnableConfigurationProperties(HttpProperties.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(CharacterEncodingFilter.class) @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) public class HttpEncodingAutoConfiguration { private final HttpProperties.Encoding properties; public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } @Bean @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; } @Bean public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() { return new LocaleCharsetMappingsCustomizer(this.properties); } ……
正因為SpringBoot在spring.factories檔案中載入的類都擁有@Conditional 的擴充套件註解,SpringBoot便可以判斷該AutoConfiguration配置類是否滿足@Conditional *所註解的前置條件,如果滿足則進行例項化,如果不滿足則跳過。
小結
本篇文章我們瞭解@Conditional 的基本使用和在SpringBoot中發揮的作用。後面我們將以具體的示例來進行詳細說明。歡迎持續關注。