@component注解和@bean,Spring Boot 條件注解@Conditional介紹

 2023-12-12 阅读 28 评论 0

摘要:一、概述 Spring Boot 是自以為是的,當 Spring Boot 在classpath中找到相關依賴項時,它會為模塊提供默認(自動)配置。 舉例,Spring Boot 提供了: 如 classpath 中未提供服務器組件依賴,則默認啟用內嵌Tomcat作為服務端依賴。

一、概述
Spring Boot 是自以為是的,當 Spring Boot 在classpath中找到相關依賴項時,它會為模塊提供默認(自動)配置。

舉例,Spring Boot 提供了:

如 classpath 中未提供服務器組件依賴,則默認啟用內嵌Tomcat作為服務端依賴。你也可以把它改成 Jetty 或 Undertow,僅需要添加相應的依賴到 classpath 中(譯者注:常見的方式就是添加Maven或Gradle依賴)
當它在classpath找到 spring-cloud-starter-openfeign 依賴會提供默認的 HttpClient 配置,你可以通過在classpath中添加 OkHttpClient 或 ApacheHttpClient 依賴來改變HttpClient
當classpath中包含 spring-boot-starter-web 的依賴,則使用 Jackson 框架作為默認的 JSON 序列化傳輸請求和響應
當classpath中包含 spring-boot-starter-data-jpa 則會創建默認的數據源配置
1.1、Spring Boot 是如何做到這一切的呢?

Spring Boot 使用 @Conditional 注解實現了這一點。Spring Boot 大量使用 @Conditional 注解基于條件方式來加載默認配置、Bean對象的。

@component注解和@bean?這些條件可以是:

classpath中可用的依賴、配置文件內容 或 類
定義在 application.yml 或 application.properties 文件、系統配置、環境變量中的配置
Java的版本、操作系統、云平臺、web應用 等。
1.2、它對我們有什么用處呢?

類似于Spring如何神奇地讀取默認的配置,有些時候我們想基于自定義的條件來讀取 Bean對象、模塊 到Spring應用上下文中。

我們可以通過 @Conditional 注解+自定義的條件實現這個目標。

二、@Conditional 注解說明
@Conditional 注解是用來匹配只有滿足所有指定條件才能將Bean注冊到Spring上下文中。

java transaction注解。@Conditional 注解有以下三種使用方式:

2.1、作為方法級的注解

我們可以在 @Bean 注解下添加 @Conditional 注解來達到滿足條件注冊Bean。

@Configuration
class ConditionalBeanConfiguration {
? @Bean
? @Conditional(CustomCondition.class) //<- method level condition
? ConditionalBean conditionalBean(){
? ? return new ConditionalBean();
? };
}
2.2、作為類級別注解

我們可以在有 @Component, @Service, @Repository, @Controller, @RestController, 或 @Configuration 注解的類上標注 @Conditional,僅當滿足條件才能將這個類型的 bean 注冊到 Spring 上下文中。

Springboot核心注解?@Component
@Conditional(CustomCondition.class) //<- class level condition
class ConditionalComponent {
}
帶@Conditional注解的@Configuration類

如果一個 @Configuration 注解標識的類頭包含 @Conditional 則這個類所有的 @Bean 方法、@Import 注解 和 @ComponentScan 注解都會被類頭上的 @Conditional 影響,僅當條件滿足時才會被加載。

@Configuration
@Conditional(CustomCondition.class) //<- class level condition
class ConditionalConfiguration {
? @Bean //<- will be loaded only if class level condition is met
? Bean bean(){
? ? // Code for bean definition
? };
}
2.3、作為源注解

我們可以創建一個自定義的注解,在注解上添加 @Conditional 作為新的條件注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(CustomCondition.class) //<- meta annotation condition
public @interface CustomConditionAnnotation {
}
現在我們可在方法和類上使用自定義的 @CustomConditionAnnotation 注解代替@Conditional 就像這樣:

jsonpropertyorder注解、@Configuration
class ConditionalBeanConfiguration {
? @Bean
? @CustomConditionAnnotation ?//<- custom annotation at method level
? ConditionalBean conditionalBean(){
? ? return new ConditionalBean();
? };
}

@Component
@CustomConditionAnnotation //<- custom annotation at class level
class ConditionalComponent {
}
三、自定義條件
在之前的例子中,我們使用 CustomCondition.class 中的 @Conditional 注解實現了條件匹配的邏輯。

讓我們通過實現 Spring 的 Condition 接口的 matches 方法創建自定義條件吧:

public class CustomCondition implements Condition {
? ? @Override
? ? public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
? ? ? ? return context.getEnvironment().getProperty("custom.condition.enabled", Boolean.class, false);
? ? }
}
配置文件:application.yml

custom.condition.enabled: true

java自定義注解使用場景?我們可以看到當 custom.condition.enabled 設為 true 時,CustomCondition 可匹配到。

3.1、組合條件-任一條件成立匹配

我們可組合多個條件,實現任一條件符合,換句話講:類似于實現 OR 的邏輯操作。

3.1.1、再創建一個注解來組合

我們已經創建了一個自定義注解CustomCondition,讓我們快速創建一個AnotherCustomCondition注解:

java component注解、public class AnotherCustomCondition implements Condition {
? ? @Override
? ? public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
? ? ? ? return context.getEnvironment().getProperty("another-custom.condition.enabled", Boolean.class, false);
? ? }
}
配置文件:application.yml

another-custom.condition.enabled: true

我們看到 another-custom.condition.enabled 條件為 true 時, AnotherCustomCondition 能匹配到。

3.1.2、AnyNestedCondition(任意嵌套條件)

現在讓我們通過擴展 Spring 的 AnyNestedCondition 類來組合上邊兩個注解,做到任一匹配:

public class CombinedConditionsWithAnyMatch extends AnyNestedCondition {

? ? public CombinedConditionsWithAnyMatch() {
? ? ? ? super(ConfigurationPhase.PARSE_CONFIGURATION);
? ? ? ? // super(ConfigurationPhase.REGISTER_BEAN);
? ? }

? ? @Conditional(CustomCondition.class)
? ? static class OnCustomCondition {}

? ? @Conditional(AnotherCustomCondition.class)
? ? static class OnAnotherCustomCondition {}
}
3.1.3、ConfigurationPhase(配置階段)說明

注意 ConfigurationPhase 參數被傳遞到了構建方法:

如果你想組合的條件到一個 @Configuration 類,使用 ConfigurationPhase.PARSE_CONFIGURATION;

如果你想把條件應用到 @Bean 對象上,使用 ConfigurationPhase.REGISTER_BEAN

Spring Boot 需要區分這以上兩種作用域以便在合適的時間應用這些條件。

3.1.4、使用任一條件匹配

讓我們來應用組合條件到一個配置類吧:

@Configuration
@Conditional(CombinedConditionsWithAnyMatch.class)
class ConditionalConfiguration {
? @Bean
? Bean bean(){
? ? // TODO
? };
}
當任一條件滿足時,這個配置類對象將被加載到Spring應用上下文中

配置文件:application.yml

custom.condition.enabled: true

another-custom.condition.enabled: false
3.2、組合條件-所有條件成立匹配

我們可以組合多個條件來達到僅當所有條件都滿足時才匹配的目的,類似于 AND 的邏輯操作。

3.2.1、AllNestedConditions 基礎類

這次我們組合 CustomCondition、AnotherCustomCondition 這兩個注解,通過擴展Spring的 AllNestedConditions 創建 匹配所有 (完全匹配)的條件類

public class CombinedConditionsWithAllMatch extends AllNestedConditions {
? ? public CombinedConditionsWithAllMatch() {
? ? ? ? // super(ConfigurationPhase.PARSE_CONFIGURATION);
? ? ? ? super(ConfigurationPhase.REGISTER_BEAN);
? ? }
? ? @Conditional(CustomCondition.class)
? ? static class OnCustomCondition {}
? ? @Conditional(AnotherCustomCondition.class)
? ? static class OnAnotherCustomCondition {}
}
3.2.2、使用完全匹配條件

這次我們將 ConfigurationPhase.REGISTER_BEAN 傳入構造方法,我們就能使用這個組合條件放到 @Bean 對象,就像下邊這樣:

@Configuration
class ConditionalBeanConfiguration {
? @Bean
? @Conditional(CombinedConditionsWithAllMatch.class) //<- as method level annotation
? ConditionalBean conditionalBean(){
? ? return new ConditionalBean();
? };
}
這個Bean僅當所有條件都滿足時才會加載到Spring應用上下文中:

配置文件:application.yml

custom.condition.enabled: true

another-custom.condition.enabled: true
3.3、組合條件-所有條件不成立匹配

我們可以通過擴展 Spring 的 NoneNestedCondition 類來組合出所有條件都不成立的條件:

public class CombinedConditionsWithNoneMatch extends NoneNestedConditions {
? ? public CombinedConditionsWithNoneMatch() {
? ? ? ? super(ConfigurationPhase.PARSE_CONFIGURATION);
? ? ? ? // or super(ConfigurationPhase.REGISTER_BEAN);
? ? }

? ? @Conditional(CustomCondition.class)
? ? static class OnCustomCondition {}

? ? @Conditional(AnotherCustomCondition.class)
? ? static class OnAnotherCustomCondition {}
}
以上組合條件將會成功匹配如下配置:

配置文件:application.yml

custom.condition.enabled: false

another-custom.condition.enabled: false
四、預定義的條件注解
Spring Boot 提供了一系列預定義的 @ConditionalOn… 便利注解供我們使用。讓我們看看有哪些:

4.1、@ConditionalOnProperty

這個是Spring Boot中最常用的注解,它允許通過指定的配置來條件式加載類或Bean:

@Configuration
@ConditionalOnProperty(
? ? value="api.doc.enabled",?
? ? havingValue = "true",?
? ? matchIfMissing = true)
class ApiDocConfig {
? // TODO
}
ApiDocConfig 僅當 api.doc.enabled: true 時才會加載,如果沒有配置這個參數,它也會被加載,因為定義了 matchIfMissing = true,通過這種方式,我們可以在沒配置指定參數時創建默認配置,設置為false時才禁用。

一個常見的例子是當我們想去在開發環境開啟一個指定配置,而生產環境禁用,反之亦然:

配置文件:application-dev.yml

api.doc.enabled: true

配置文件:application-prod.yml

api.doc.enabled: false

(譯者注:通過不同配置文件區分環境,由條件注解判斷是否加載類或對象到Spring上下文)

另一個常見的例子是定義了一個公共組件,通過不同項目的配置文件可以在開啟或禁用這個功能,配置示例如下:

Project A -> application.yml
api.doc.enabled: true

Project B -> application.yml
api.doc.enabled: false
4.2、@ConditionalOnExpression

如果我們需要多個配置項組合更復雜的條件,我們可以使用 @ConditionalOnExpression (譯者注:表達式條件注解):

@Configuration
@ConditionalOnExpression(
? "${api.doc.enabled:true} and '${spring.profile.active}'.equalsIgnoreCase('DEV')"
)
class ApiDocConfig {
? // TODO
}
這個 ApiDocConfig 僅當 api.doc.enabled: true 并且 spring.profile.active: dev 時才會啟用。我們告訴 Spring Boot 使用 true作為默認值適用于參數未設置的場景。

我們可以在這個注解中更充分地發揮 Spring Expression Language 的作用。

4.3、@ConditionalOnBean

我們可能需要僅當某個Bean依賴的Bean存在應用上下文中才創建這個Bean:

@Service
@ConditionalOnBean(ApiDocConfig.class)
class ApiDocService {
? ? // TODO
}
這個ApiDocService 僅當 ApiDocConfig 這個類的對象在應用上下文中才會加載。這個方法使我們可以定義依賴其他Bean的對象。(譯者注:依賴對象存在則創建,否則不創建)

4.4、@ConditionalOnMissingBean

類似于上邊的例子,可以使用 @ConditionalOnMissingBean 來確保應用上下文中沒有某個對象才創建:

@Configuration
class DatabaseConfig {
? @Bean
? @ConditionalOnMissingBean
? DataSource dataSource() {
? ? return new InMemoryDataSource();
? }
}
這個例子演示了當應用上下文中沒有其他 DataSource 對象時,創建 InMemoryDataSource這個對象,這和Spring Boot 在測試上下文創建內存數據源是類似的。

4.5、@ConditionalOnResource

這個注解僅當指定資源文件在 classpath 中是能找到的,才會加載這個配置

@Configuration
@ConditionalOnResource(resources = "/logback.xml")
class LoggingConfig {
? // TODO
}
這個 LoggingConfig 僅當 logback.xml 配置文件存在于 classpath 時才會加載。這樣一來,我們能創建相似配置類,僅當它們的配置文件存在配置類才可用。

上面描述的條件注解是我們在 Spring Boot 項目中通常使用的最常見的注解。 Spring Boot 提供了許多其他的條件注解。 然而,它們并不常見,更適合框架開發而不是應用程序開發(Spring Boot 在后臺大量使用其中的一些)。 所以,讓我們在這里簡單地看一下它們。

4.6、@ConditionalOnClass

僅當指定類在classpath下才會加載這個bean,我們可指定類的全路徑或這個類的名稱

@Service
@ConditionalOnClass(name = "com.example.config.LoggingConfig")
class LoggingService {
? // Code runs when class in available in classpath
}

@Service
@ConditionalOnClass(LoggingConfig.class)
class LoggingService {
? // Code runs when class in available in classpath
}
相似的,我們也可以用 @ConditionalOnMissingClass 注解來加載一個需要某個類不在classpath 下時才加載的bean。

4.7、@ConditionalOnWebApplication

僅當此應用是一個web應用時加載這個bean,默認只要是web容器都會匹配,也可以使用type 屬性來縮小范圍。(譯者注:type 共有3種,ANY 表示任何 web 容器,范圍最大;SERVLET表示僅 servlet 容器;“REACTIVE` 表示僅響應式容器)

@Configuration
@ConditionalOnWebApplication
class RunsOnAnyWebApplication {
? // Code runs on web application
}

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
class RunsOnServletBasedWebApplication {
? // Code runs on Servlet based web application
}
類似地,也可以用 @ConditionalOnNotWebApplication 來達到非 Web 容器才加載 bean 的目的。

4.8、@ConditionalOnJava

僅在等于或高于指定JVM版本時才加載某個 bean。默認是等于或高于指定版本,也可以設置為 range 屬性表示一個版本區間。

@Configuration
@ConditionalOnJava(JavaVersion.EIGHT)
class RunsOnJavaEightAndAbove {
? // Code runs on Java-8 and above versions
}

@Configuration
@ConditionalOnJava(value = JavaVersion.ELEVEN, range = ConditionalOnJava.Range.OLDER_THAN)
class RunsOnBelowJavaEleven {
? // Code runs below Java-11.?
? // Code doesn't run on Java-11 and above versions
}
4.9、@ConditionalOnCloudPlatform

僅在指定云平臺上運行應用時才會加載此注解標記的 bean

@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
class RunsOnKubernetesCloudPlatform {
? // Code runs on application running on Kubernetes
}
4.10、@ConditionalOnWarDeployment

僅在傳統 War 包打包部署時加載該注解標記的 bean。在應用以內嵌服務器運行時這個條件返回false。

@Configuration
@ConditionalOnWarDeployment
class RunsWithWarPackages {
? // Code runs with WAR package deployment
}
4.11、@ConditionalOnJndi

僅在指定的 JNDI 路徑存在時加載該注解標記的 bean。如果沒有路徑指定,則條件匹配僅基于 javax.naming.InitialContext 的存在。

@Configuration
@ConditionalOnJndi("java:comp/env/ejb/myEJB")
class RunsWithJndiLocationAvailability {
? // Code runs when JNDI location is available
}
4.12、@ConditionalOnSingleCandidate

與 @ConditionalOnBean 類似,但它是僅在這個 bean 是唯一的候選時啟用。如果 BeanFactory 中已包含多個匹配的 bean 實例但已定義主 @Primary 候選者,則條件也將匹配;本質上,如果自動裝配具有定義類型的 bean,則條件匹配將成功。 強烈建議僅在自動配置類上使用此條件。(譯者注:有一個或多個中有主候選的 bean 將匹配成功)

@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
class RunsWithSingleDataSourceBean {
? // Code runs when single data source bean is determined
}
4.13、@ConditionalOnManagementPort

僅當 management.server.port條件與 server.port 具體某種關系時加載bean。

有三種關系類型:

ManagementPortType.DISABLED:禁用或沒定義管理端口
ManagementPortType.SAME:服務端口與管理端口相同
ManagementPortType.DIFFERENT:服務與管理端口不同
@Configuration
@ConditionalOnManagementPort(ManagementPortType.DISABLED)
class ManagementPortIsDisabled {
? // Code runs when management port is disabled
}

@Configuration
@ConditionalOnManagementPort(ManagementPortType.SAME)
class ManagementPortIsSameAsServerPort {
? // Code runs when management port is same as server port
}

@Configuration
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
class ManagementPortIsDifferentFromServerPort {
? // Code runs when management port is different from server port
}

4.14、@ConditionalOnAvailableEndpoint

當指定的管理端點(譯者注:接口地址)可用時加載 bean。如果一個端點被單獨啟用或使用 management.endpoints.web.exposure.include 暴露出來,都視為可用。

@Configuration
@ConditionalOnAvailableEndpoint(endpoint = InfoEndpoint.class)
class InfoEndpointIsAvailable {
? // Code runs when info management endpoint in enabled and exposed
}
端點是由 @Endpoint 或 @EndpointExtension 標記的bean。info 端點由 Spring Boot 提供,開箱即用。

4.14.1、Custom Management Endpoint

你也可以創建自定義的管理端點,就像這個例子:

@Component
@Endpoint(id = "custom-endpoint")
public class CustomEndpoint {
? ? @ReadOperation
? ? public String print() {
? ? ? ? return "This is custom management endpoint";
? ? }
}
現在讓我們寫一個僅當自定義端點可用時加載的配置類:

@Configuration
@ConditionalOnAvailableEndpoint(endpoint = CustomEndpoint.class)
class CustomEndpointConfiguration {
? // Configuration for custom endpoint
}
4.15、@ConditionalOnEnabledHealthIndicator

僅當健康指示器配置 management.health. .enabled 啟用時才加載此健康檢測指示器類, 需要指定為具體的值。

@Configuration
@ConditionalOnEnabledHealthIndicator(value = "heartbeat")
class HeatbeatHealthIndicator {
? // Code runs when management.health.heartbeat.enabled property is set to true.
}
五、總結
Conditional 注解給了 Spring Boot 提供了自以為是的配置(譯者注:默認配置),給我們提供基于 @Conditional 自定義條件加載類的靈活性,以及非常便捷的 @ConditionalOn… 注解。

我們也可通過 AllNestedConditions、AnyNestedCondition、NoneNestedCondition 組合自定義的條件,它們為我們提供了基于環境和其他條件的模塊化編碼方式。

我們應該謹慎地使用 Conditional 注解,因為過度使用他們會導致難于調試與管理。
?

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/196287.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息