SpringBoot的@Conditional使用 - reflectoring
在開發Spring Boot應用程式時,如果滿足某些條件,我們有時只想將bean或模組載入到應用程式上下文中。然後在測試期間禁用某些bean,或者在執行時環境中對某個屬性做出反應。
Spring引入了@Conditional註釋,允許我們定義自定義條件以應用於應用程式上下文的各個部分。Spring Boot構建於此之上,並提供一些預定義的條件,因此我們不必自己實現它們。
在本教程中,我們將看一些用例,解釋為什麼我們需要條件載入的bean。然後,我們將看到如何應用條件以及Spring Boot提供的條件。為了解決問題,我們還將實現自定義條件。
為什麼我們需要有條件的豆?
Spring應用程式上下文包含一個物件圖,它構成了我們的應用程式在執行時需要的所有bean。Spring的@Conditional註釋允許我們定義將某個bean包含在該物件圖中的條件。
為什麼我們需要在某些條件下包含或排除bean?
根據我的經驗,最常見的用例是某些bean在測試環境中不起作用。它們可能需要連線到遠端系統或測試期間不可用的應用程式伺服器。因此,我們希望模組化我們的測試 以在測試期間排除或替換這些bean。
另一個用例是我們想要啟用或禁用某個跨領域的問題。想象一下,我們已經構建了一個配置安全性的模組。在開發人員測試期間,我們不希望每次都輸入我們的使用者名稱和密碼,因此我們使用一個開關並禁用整個安全模組進行本地測試。
此外,我們可能只想在某些外部資源可用時才載入某些bean ,否則它們將無法工作。例如,我們只想logback.xml在類路徑中找到檔案時配置我們的Logback記錄器。
我們將在下面的討論中看到更多用例。
定義有條件的Bean
在我們定義Spring bean的任何地方,我們都可以選擇新增條件。只有滿足此條件,才會將bean新增到應用程式上下文中。要宣告條件,我們可以使用下面@Conditional...描述的任何註釋。
但首先,讓我們看一下如何將條件應用於某個Spring bean。
如果我們向單個@Bean定義新增條件,則僅在滿足條件時才載入此bean:
@Configuration class ConditionalBeanConfiguration { @Bean @Conditional... // <-- ConditionalBean conditionalBean(){ return new ConditionalBean(); }; } |
如果我們向Spring新增一個條件@Configuration,那麼只有在滿足條件時才會載入此配置中包含的所有bean:
@Configuration @Conditional... // <-- class ConditionalConfiguration { @Bean Bean bean(){ ... }; } |
我們可以新增一個條件到@Component,@Service,@Repository,或@Controller:
@Component @Conditional... // <-- class ConditionalComponent { } |
預先定義的條件
Spring Boot提供了一些@ConditionalOn...我們可以開箱即用的預定義註釋。讓我們依次看看每一個。
@ConditionalOnProperty
根據我的經驗,@ConditionalOnProperty註釋是Spring Boot專案中最常用的條件註釋。它允許根據特定的環境屬性有條件地載入bean:
@Configuration @ConditionalOnProperty( value="module.enabled", havingValue = "true", matchIfMissing = true) class CrossCuttingConcernModule { ... } |
這個CrossCuttingConcernModule只載入module.enabled屬性取值為true的Bean。如果沒有設定該屬性,它仍將被載入,因為我們已定義matchIfMissing 為true。這樣,我們建立了一個預設載入的模組,直到我們另行決定。
同樣地,我們可能會建立其他模組來解決我們可能希望在某個(測試)環境中禁用的安全性或排程等交叉問題。
@ConditionalOnExpression
如果我們有基於多個屬性的更復雜的條件,我們可以使用@ConditionalOnExpression:
@Configuration @ConditionalOnExpression( "${module.enabled:true} and ${module.submodule.enabled:true}" ) class SubModule { ... } |
如果module.enabled和module.submodule.enabled 都具價值true,則載入。透過附加:true到屬性,我們告訴Spring true 在未設定屬性的情況下將其用作預設值。我們可以使用Spring Expression Language的完整擴充套件。
這樣,我們可以建立子模組,如果父模組被禁用,則應該禁用這些子模組,但如果啟用了父模組,也可以禁用子模組。
@ConditionalOnBean
有時,我們可能只想在應用程式上下文中某個其他bean可用時才載入bean:
@Configuration @ConditionalOnBean(OtherModule.class) class DependantModule { ... } |
DependantModule 只有在上下文存在OtherModule 時才載入。
我們也可以定義bean名稱而不是bean類。
這樣,我們可以定義某些模組之間的依賴關係。僅當另一個模組的某個bean可用時才載入一個模組。
@ConditionalOnMissingBean
類似地,如果我們只想在某個其他bean 不在應用程式上下文中時載入bean ,我們就可以使用@ConditionalOnMissingBean:
@Configuration class OnMissingBeanModule { @Bean @ConditionalOnMissingBean DataSource dataSource() { return new InMemoryDataSource(); } } |
在此示例中,如果還沒有可用的資料來源,我們只會將記憶體中的資料來源注入應用程式上下文。這與Spring Boot在內部提供的測試上下文中的記憶體資料庫非常相似。
@ConditionalOnResource
如果我們想根據類路徑上某個資源可用的事實載入bean,我們可以使用@ConditionalOnResource:
@Configuration @ConditionalOnResource(resources = "/logback.xml") class LogbackModule { ... } |
如果在類路徑中配置了logback檔案就載入LogbackModule。這樣,我們可能會建立類似的模組,只有在找到相應的配置檔案時才會載入這些模組。
其他條件
上面描述的條件註釋是我們可能在任何Spring Boot應用程式中使用的更常見的註釋。Spring Boot提供了更多的條件註釋。但是,它們並不常見,有些更適合框架開發而不是應用程式開發(Spring Boot大量使用它們)。所以,我們在這裡只是簡單地看一下它們。
@ConditionalOnClass:僅當類路徑上有某個類時才載入bean:
@Configuration @ConditionalOnClass(name = "this.clazz.does.not.Exist") class OnClassModule { ... } |
@ConditionalOnMissingClass:僅當某個類不在類路徑上時才載入bean :
@Configuration @ConditionalOnMissingClass(value = "this.clazz.does.not.Exist") class OnMissingClassModule { ... } |
@ConditionalOnJndi:僅當透過JNDI提供某個資源時才載入bean:
@Configuration @ConditionalOnJndi("java:comp/env/foo") class OnJndiModule { ... } |
@ConditionalOnJava:僅在執行特定版本的Java時載入bean:
@Configuration @ConditionalOnJava(JavaVersion.EIGHT) class OnJavaModule { ... } |
@ConditionalOnSingleCandidate:類似於@ConditionalOnBean,但只有在確定了給定bean類的單個候選項時才會載入bean。可能沒有自動配置之外的用例:
@Configuration @ConditionalOnSingleCandidate(DataSource.class) class OnSingleCandidateModule { ... } |
@ConditionalOnWebApplication:僅當我們在Web應用程式中執行時才載入bean:
@Configuration @ConditionalOnWebApplication class OnWebApplicationModule { ... } |
@ConditionalOnNotWebApplication:僅當我們沒有在Web應用程式中執行時才載入bean :
@Configuration @ConditionalOnNotWebApplication class OnNotWebApplicationModule { ... } |
@ConditionalOnCloudPlatform:僅當我們在某個雲平臺上執行時才載入bean:
@Configuration @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) class OnCloudPlatformModule { ... } |
自定義條件
除了條件註釋,我們可以建立自己的註釋,並將多個條件與邏輯運算子組合在一起。
想象一下,我們有一些Spring bean本身可以與作業系統對話。只有在我們在相應的作業系統上執行應用程式時才應載入這些bean。
讓我們實現一個條件,只有當我們在unix機器上執行程式碼時才載入bean。為此,我們實現了Spring的Condition 介面:
class OnUnixCondition implements Condition { @Override public boolean matches( ConditionContext context, AnnotatedTypeMetadata metadata) { return SystemUtils.IS_OS_LINUX; } } |
我們只是使用Apache Commons的SystemUtils類來確定我們是否在類似unix的系統上執行。如果需要,我們可以包含更復雜的邏輯,它使用有關當前應用程式上下文(ConditionContext)或有關注釋類(AnnotatedTypeMetadata)的資訊。
現在可以將條件與Spring的@Conditional註釋結合使用了:
@Bean @Conditional(OnUnixCondition.class) UnixBean unixBean() { return new UnixBean(); } |
將條件與OR結合:
如果我們想要將多個條件與邏輯“OR”運算子組合成一個條件,我們可以擴充套件AnyNestedCondition:
class OnWindowsOrUnixCondition extends AnyNestedCondition { OnWindowsOrUnixCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @Conditional(OnWindowsCondition.class) static class OnWindows {} @Conditional(OnUnixCondition.class) static class OnUnix {} } |
在這裡,我們建立了一個條件,如果應用程式在Windows或unix上執行,則滿足該條件。
在AnyNestedCondition父類將評估@Conditional的方法說明和使用OR運算子將它們結合起來。
我們可以像任何其他條件一樣使用這個條件:
@Bean @Conditional(OnWindowsOrUnixCondition.class) WindowsOrUnixBean windowsOrUnixBean() { return new WindowsOrUnixBean(); } |
注:你AnyNestedCondition還是AllNestedConditions不工作?
檢查ConfigurationPhase傳入的引數super()。如果要將組合條件應用於@Configurationbean,請使用該值 PARSE_CONFIGURATION。如果要將條件應用於簡單bean,請使用REGISTER_BEAN上面的示例中所示。Spring Boot需要進行區分,以便它可以在應用程式上下文啟動期間的適當時間應用條件。
將條件與AND結合起來:
如果我們想要將條件與“AND”邏輯結合起來,我們可以簡單地@Conditional...在單個bean上使用多個 註釋。它們將自動與邏輯“AND”運算子組合,這樣如果至少有一個條件失敗,則不會載入bean:
@Bean @ConditionalOnUnix @Conditional(OnWindowsCondition.class) WindowsAndUnixBean windowsAndUnixBean() { return new WindowsAndUnixBean(); } |
這個bean永遠不應該載入,除非有人建立了我不知道的Windows / Unix混合。
請注意,@Conditional註釋不能在單個方法或類上多次使用。因此,如果我們想以這種方式組合多個註釋,我們必須使用@ConditionalOn...沒有此限制的自定義註釋。下面,我們將探討如何建立@ConditionalOnUnix註釋。
或者,如果我們想將條件與AND組合成一個 @Conditional註釋,我們可以擴充套件Spring Boot的AllNestedConditions 類,其工作方式與AnyNestedConditions上述完全相同。
結合條件與NOT:
與AnyNestedCondition和類似AllNestedConditions,NoneNestedCondition如果組合條件中的NONE匹配,我們可以擴充套件到僅載入bean。
定義定製的@ ConditionalOn ...註釋
我們可以為任何條件建立自定義註釋。我們只需要使用以下方法對此註釋進行元註釋@Conditional:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnLinuxCondition.class) public @interface ConditionalOnUnix {} |
當我們用新的註釋註釋bean時,Spring將評估使用這個元註釋:
@Bean @ConditionalOnUnix LinuxBean linuxBean(){ return new LinuxBean(); } |
結論
透過@Conditional註釋和建立自定義@Conditional... 註釋的可能性,Spring已經為我們提供了很多控制應用程式上下文內容的能力。
春天引導建立在最重要的是透過將一些方便的@ConditionalOn...註解表,並透過允許我們使用條件相結合AllNestedConditions,AnyNestedCondition或NoneNestedCondition。這些工具允許我們模組化我們的生產程式碼以及我們的測試。
然而,權力是責任,所以我們應該注意不要在條件下亂丟我們的應用程式上下文,以免我們忘記何時載入。
本文的程式碼可以在github上找到。
相關文章
- SpringBoot(15)—@Conditional註解Spring Boot
- 使用jEnv管理多個JDK安裝 - reflectoringJDK
- Reflection Conditional expectation
- Conditional AutoEncoder的Pytorch完全實現PyTorch
- TypeScript 之 Conditional TypesTypeScript
- Java HTTP 客戶端的比較 - reflectoringJavaHTTP客戶端
- 【shell 】syntax error in conditional expressionErrorExpress
- Spring註解之@ConditionalSpring
- Java和Spring的六邊形架構 - reflectoringJavaSpring架構
- SpringBoot AOP的使用Spring Boot
- 使用Spring Boot的Configuration和ArchUnit實現元件模組化和清晰邊界 - reflectoringSpring Boot元件
- Spring條件註解@ConditionalSpring
- 使用Spring Boot和Swagger進行API優先開發 - reflectoring.ioSpring BootSwaggerAPI
- 結合Hazelcast和Spring的分散式快取 - reflectoringASTSpring分散式快取
- SpringBoot 中 JPA 的使用Spring Boot
- springboot中RedisTemplate的使用Spring BootRedis
- SpringBoot(三)_controller的使用Spring BootController
- Bean驗證反模式 - reflectoring.ioBean模式
- Spring Boot應用程式事件教程 - reflectoringSpring Boot事件
- 如何使用Spring Boot和Flyway建立不同資料庫的多租戶應用? - reflectoring.ioSpring Boot資料庫
- 使用Liquibase和Spring Boot進行資料庫遷移的一站式指南 - reflectoringUISpring Boot資料庫
- SpringBoot之:SpringBoot中使用HATEOASSpring Boot
- Mybatis 的使用(整合Spring、SpringBoot)MyBatisSpring Boot
- SpringBoot+Redis的基本使用Spring BootRedis
- 使用IDEA的SpringBoot整合JDBCIdeaSpring BootJDBC
- 【SpringBoot】@Configration與@Bean的使用Spring BootBean
- SpringBoot中使用jsp的坑Spring BootJS
- Spring Framework 條件裝配 之 @ConditionalSpringFramework
- SpringBoot使用JdbcTemplateSpring BootJDBC
- SpringBoot使用LomBokSpring BootLombok
- SpringBoot使用JPASpring Boot
- SpringBoot使用WebSocketSpring BootWeb
- springboot 使用 rocketMQSpring BootMQ
- SpringBoot使用RedisSpring BootRedis
- SpringBoot使用WebJarsSpring BootWebJAR
- SpringBoot使用AOPSpring Boot
- SpringBoot圖文教程6—SpringBoot中過濾器的使用Spring Boot過濾器
- SpringBoot之ApplicationContextInitializer的理解和使用Spring BootAPPContext