1. 簡介
本教程將介紹 @Conditional 註解。它用於指示某個組件是否根據定義的條件具有註冊資格。
我們將學習如何使用預定義的條件註解,以及如何將它們與不同的條件組合,並創建自定義的基於條件的註解。
2. 聲明條件
在深入實施之前,讓我們看看我們如何利用條件註解在哪些情況下發揮作用。
最常見的用法是包含或排除整個配置類:
@Configuration
@Conditional(IsDevEnvCondition.class)
class DevEnvLoggingConfiguration {
// ...
}
或者僅僅是一個豆子:
@Configuration
class DevEnvLoggingConfiguration {
@Bean
@Conditional(IsDevEnvCondition.class)
LoggingService loggingService() {
return new LoggingService();
}
}通過這樣做,我們可以根據給定的條件來定義應用程序的行為,例如環境類型或客户的具體需求。在上例中,我們僅為開發環境初始化了額外的日誌服務。
另一種使組件條件化的方法是直接在組件類中放置條件:
@Service
@Conditional(IsDevEnvCondition.class)
class LoggingService {
// ...
}我們可以在聲明瞭 @Component、@Service、@Repository 或 @Controller 註解的任何 Bean 上應用上述示例。
3. 預定義的條件註解
Spring 提供了若干預定義的條件註解。下面我們來了解一些最常用的註解。
首先,我們來看如何根據配置屬性值來基於組件:
@ConfigurationProperties // 示例代碼註釋
@Service
@ConditionalOnProperty(
value="logging.enabled",
havingValue = "true",
matchIfMissing = true)
class LoggingService {
// ...
}第一個屬性,, 告訴我們我們將要查看的配置屬性。第二個屬性,, 定義了用於此條件的所需值。最後, 屬性告訴 Spring 如果參數缺失,條件是否應匹配。
同樣,我們可以基於一個表達式來定義條件:
@Service
@ConditionalOnExpression(
"${logging.enabled:true} and '${logging.level}'.equals('DEBUG')"
)
class LoggingService {
// ...
}現在 Spring 只會在 LoggingService 被創建,前提是 logging.enabled 配置屬性設置為 true, 並且 logging.level 設置為 DEBUG.
另一個可以應用的條件是檢查給定類是否在類路徑上存在:
@Service
@ConditionalOnClass(CustomLogger.class)
class LoggingService {
// ...
}我們可以通過應用 @ConditionalOnMissingBean 或 @ConditionalOnMissingClass 註解來實現相反的行為。
此外,我們可以使我們的組件依賴於特定的 Java 版本:
@Service
@ConditionalOnJava(JavaVersion.EIGHT)
class LoggingService {
// ...
}在上例中,LoggingService 只會在運行時環境為 Java 8 時才創建。
我們還可以使用 @ConditionalOnWarDeployment 註解,僅在 WAR 部署包中啓用 Bean:
@Configuration
@ConditionalOnWarDeployment
class AdditionalWebConfiguration {
// ...
}請注意,對於嵌入了服務器的應用,此條件將返回 false。
此外,Spring Boot 提供了 @ConditionalOnBean 註解。與我們之前見過的其他 @ConditionalOn* 註解不同,@ConditionalOnBean 只能與通過應用程序上下文已處理的 Bean 定義相匹配。因此,我們應該僅將其用於自動配置類。關於 Spring Boot 自動配置,我們有一篇專門的文章來解釋如何創建自定義自動配置。
4. 定義自定義條件
Spring 允許我們通過 @Conditional 註解的行為,通過 創建自定義條件模板。 要創建一個,我們只需要實現 Condition 接口:
class Java8Condition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return JavaVersion.getJavaVersion().equals(JavaVersion.EIGHT);
}
}matches 方法告訴 Spring 條件是否通過。它有兩個參數,提供有關 bean 初始化上下文以及所使用的 @Conditional 註解的元數據的信息。
如我們例子所示,我們只是檢查 Java 版本是否為 8。
之後,我們應該將新的條件作為屬性添加到 @Conditional 註解中:
@Service
@Conditional(Java8Condition.class)
public class Java8DependedService {
// ...
}這樣一來,Java8DependentService 只會在 Java8Condition 類中的條件匹配時才會被創建。
5. 組合條件
為了更復雜的解決方案,我們可以使用 OR 或 AND 邏輯運算符將條件註解組合起來。
為了應用 OR 運算符,我們需要創建一個自定義條件,該條件擴展了 <a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.html" target="_blank">AnyNestedCondition</a> 類。 在其中,我們需要為每個條件創建一個空靜態類,並使用適當的@Conditional` 實現進行標註。
例如,讓我們創建一個要求 Java 8 或 Java 9 的條件:
class Java8OrJava9 extends AnyNestedCondition {
Java8OrJava9() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@Conditional(Java8Condition.class)
static class Java8 { }
@Conditional(Java9Condition.class)
static class Java9 { }
}AND 運算符相比之下要簡單得多。我們可以簡單地將條件分組:
@Service
@Conditional({IsWindowsCondition.class, Java8Condition.class})
@ConditionalOnJava(JavaVersion.EIGHT)
public class LoggingService {
// ...
}在上述示例中,LoggingService 只會在 IsWindowsCondition 和 Java8Condition 匹配時才創建。
6. 總結