SpringBoot基礎篇Bean之條件注入@Condition使用姿勢

一灰灰發表於2018-10-21

前面幾篇關於Bean的基礎博文中,主要集中在Bean的定義和使用,但實際的情況中有沒有一些場景是不載入我定義的bean,或者只有滿足某些前提條件的時候才載入我定義的Bean呢?

本篇博文將主要介紹bean的載入中,條件註解@Conditional的相關使用

I. @Conditional註解

這個註解在Spring4中引入,其主要作用就是判斷條件是否滿足,從而決定是否初始化並向容器註冊Bean

1. 定義

@Conditional註解定義如下,其內部主要就是利用了Condition介面,來判斷是否滿足條件,從而決定是否需要載入Bean

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}
複製程式碼

下面是Condtion介面的定義,這個可以說是最基礎的入口了,其他的所有條件註解,歸根結底,都是通過實現這個介面進行擴充套件的

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
複製程式碼

這個介面中,有個引數比較有意思ConditionContext,它持有不少有用的物件,可以用來獲取很多系統相關的資訊,來豐富條件判斷,介面定義如下

public interface ConditionContext {
    // 獲取Bean定義
    BeanDefinitionRegistry getRegistry();

    // 獲取Bean工程,因此就可以獲取容器中的所有bean
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();

    // environment 持有所有的配置資訊
    Environment getEnvironment();
    
    // 資源資訊
    ResourceLoader getResourceLoader();

    // 類載入資訊
    @Nullable
    ClassLoader getClassLoader();
}
複製程式碼

2. 使用說明

通過一個小例子,簡單的說一下如何使用Condition和@Conditional註解,來實現bean的條件載入

首先我們定義一個隨機產生資料的類,其功能就是隨機生成一些資料

public class RandDataComponent<T> {
    private Supplier<T> rand;

    public RandDataComponent(Supplier<T> rand) {
        this.rand = rand;
    }

    public T rand() {
        return rand.get();
    }
}
複製程式碼

我們目前提供兩種隨機資料生成的bean,但是需要根據配置來選擇具體選中的方式,因此我們如下定義Bean

@Configuration
public class ConditionalAutoConfig {

    @Bean
    @Conditional(RandIntCondition.class)
    public RandDataComponent<Integer> randIntComponent() {
        return new RandDataComponent<>(() -> {
            Random random = new Random();
            return random.nextInt(1024);
        });
    }

    @Bean
    @Conditional(RandBooleanCondition.class)
    public RandDataComponent<Boolean> randBooleanComponent() {
        return new RandDataComponent<>(() -> {
            Random random = new Random();
            return random.nextBoolean();
        });
    }
}
複製程式碼

上面的配置,先不管@Conditional註解的內容,單看兩個Bean的定義,一個是定義int隨機數生成;一個是定義boolean隨機生成;

但是我們的系統中,只需要一個隨機資料生成器即可,我們選擇根據配置conditional.rand.type的值來選擇到底用哪個,配置如下

# int 表示選擇隨機產生int資料; 非int 表示隨機產生boolean資料
conditional.rand.type=int
複製程式碼

接下來就得看這個條件如何加上了,也就是上面配置類ConditionalAutoConfig中兩個註解的內容了,兩個類都是實現Condition的介面,具體如下

public class RandBooleanCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
        return "boolean".equalsIgnoreCase(type);
    }
}

public class RandIntCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        String type = conditionContext.getEnvironment().getProperty("conditional.rand.type");
        return "int".equalsIgnoreCase(type);
    }
}
複製程式碼

上面的實現也比較清晰,獲取配置值,然後判斷,並返回true/fase;返回true,則表示這個條件滿足,那麼這個Bean就可以被載入了;否則這個Bean就不會建立

3. 測試與驗證

針對上面的配置與實現,寫一個測試類如下

@RestController
@RequestMapping(path = "/conditional")
public class ConditionalRest {

    @Autowired
    private RandDataComponent randDataComponent;

    @GetMapping(path = "/show")
    public String show() {
        String type = environment.getProperty("conditional.rand.type");
        return randDataComponent.rand() + " >>> " + type;
    }
}
複製程式碼

當配置檔案的值為int時,每次訪問返回的應該都是正整數,演示如下圖

int隨機生成

將配置的值改成boolean之後,再次測試如下圖

boolean隨機生成

II. 擴充套件與小結

上面的測試演示了通過配置檔案選擇注入Bean的情況,如果一個Bean是通過自動掃描載入的,是否可以直接在Bean的類上新增註解來決定是否載入呢?

1. 自動掃描Bean的條件載入

從使用來講,和前面的沒有什麼區別,只是將註解放在具體的類上而言,同樣給出一個示例,先定義一個bean

@Component
@Conditional(ScanDemoCondition.class)
public class ScanDemoBean {

    @Value("${conditional.demo.load}")
    private boolean load;

    public boolean getLoad() {
        return load;
    }
}
複製程式碼

對應的判斷條件如下,當配置檔案中conditional.demo.load為true時,才會載入這個配置,否則不例項化

public class ScanDemoCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return "true".equalsIgnoreCase(conditionContext.getEnvironment().getProperty("conditional.demo.load"));
    }
}
複製程式碼

測試類和前面差不多,稍微注意下的就是自動注入時,改一下必要條件,避免bean不存在時報錯

@Autowired(required = false)
private ScanDemoBean scanDemoBean;

@GetMapping(path = "/scan")
public String showDemo() {
    String type = environment.getProperty("conditional.demo.load");
    if (scanDemoBean == null) {
        return "not exists! >>>" + type;
    } else {
        return "load : " + scanDemoBean.getLoad() + " >>>" + type;
    }
}
複製程式碼

當配置為true時,bean應該存在,走上面的else邏輯

自動掃描條件準確

當配置為false時,不會載入bean,走if邏輯

自動掃描新增不符

2. 小結

通過@Conditional註解配合Condition介面,來決定給一個bean是否建立和註冊到Spring容器中,從而實現有選擇的載入bean

a. 優勢

這樣做的目的是什麼呢?

  • 當有多個同名bean時,怎麼抉擇的問題
  • 解決某些bean的建立有其他依賴條件的case

b. 更多註解

上面可以控制bean的建立,但通過上面的流程,會發現有一點繁瑣,有沒有什麼方式可以簡化上面的流程呢?

只用一個註解就好,不要自己再來實現Condtion介面,Spring框架提供了一系列相關的註解,如下表

註解 說明
@ConditionalOnSingleCandidate 當給定型別的bean存在並且指定為Primary的給定型別存在時,返回true
@ConditionalOnMissingBean 當給定的型別、類名、註解、暱稱在beanFactory中不存在時返回true.各型別間是or的關係
@ConditionalOnBean 與上面相反,要求bean存在
@ConditionalOnMissingClass 當給定的類名在類路徑上不存在時返回true,各型別間是and的關係
@ConditionalOnClass 與上面相反,要求類存在
@ConditionalOnCloudPlatform 當所配置的CloudPlatform為啟用時返回true
@ConditionalOnExpression spel表示式執行為true
@ConditionalOnJava 執行時的java版本號是否包含給定的版本號.如果包含,返回匹配,否則,返回不匹配
@ConditionalOnProperty 要求配置屬性匹配條件
@ConditionalOnJndi 給定的jndi的Location 必須存在一個.否則,返回不匹配
@ConditionalOnNotWebApplication web環境不存在時
@ConditionalOnWebApplication web環境存在時
@ConditionalOnResource 要求制定的資源存在

III. 其他

0. 相關

a. 更多博文

基礎篇

應用篇

b. 專案原始碼

1. 一灰灰Blog

一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

2. 宣告

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

一灰灰blog

QrCode

相關文章