天天看點

【String注解驅動開發】如何按照條件向Spring容器中注冊bean?這次我懂了!!

【String注解驅動開發】如何按照條件向Spring容器中注冊bean?這次我懂了!!

寫在前面

當bean是單執行個體,并且沒有設定懶加載時,Spring容器啟動時,就會執行個體化bean,并将bean注冊到IOC容器中,以後每次從IOC容器中擷取bean時,直接傳回IOC容器中的bean,不再建立新的bean。

如果bean是單執行個體,并且使用@Lazy注解設定了懶加載,則Spring容器啟動時,不會執行個體化bean,也不會将bean注冊到IOC容器中,隻有第一次擷取bean的時候,才會執行個體化bean,并且将bean注冊到IOC容器中。

如果bean是多執行個體,則Spring容器啟動時,不會執行個體化bean,也不會将bean注冊到IOC容器中,以後每次從IOC容器中擷取bean時,都會建立一個新的bean傳回。

Spring支援按照條件向IOC容器中注冊bean,滿足條件的bean就會被注冊到IOC容器中,不滿足條件的bean就不會被注冊到IOC容器中。接下來,我們就一起來探讨Spring中如何實作按照條件向IOC容器中注冊bean。

項目工程源碼已經送出到GitHub:

https://github.com/sunshinelyz/spring-annotation

@Conditional注解概述

@Conditional注解可以按照一定的條件進行判斷,滿足條件向容器中注冊bean,不滿足條件就不向容器中注冊bean。

@Conditional注解是由 SpringFramework 提供的一個注解,位于 org.springframework.context.annotation 包内,定義如下。

package org.springframework.context.annotation;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Conditional {

Class<? extends Condition>[] value();           

}

從@Conditional注解的源碼來看,@Conditional注解可以添加到類上,也可以添加到方法上。在@Conditional注解中,存在一個Condition類型或者其子類型的Class對象數組,Condition是個啥?我們點進去看一下。

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;

import org.springframework.core.type.AnnotatedTypeMetadata;

@FunctionalInterface

public interface Condition {

boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);           

可以看到,Condition是一個函數式接口,對于函數式接口不了解的同學可以參見【Java8新特性】中的《【Java8新特性】還沒搞懂函數式接口?趕快過來看看吧!》一文。也可以直接檢視《Java8新特性專欄》來系統學習Java8的新特性。

是以,我們使用@Conditional注解時,需要一個類實作Spring提供的Condition接口,它會比對@Conditional所符合的方法,然後我們可以使用我們在@Conditional注解中定義的類來檢查。

@Conditional注解的使用場景如下所示。

可以作為類級别的注解直接或者間接的與@Component相關聯,包括@Configuration類;

可以作為元注解,用于自動編寫構造性注解;

作為方法級别的注解,作用在任何@Bean方法上。

向Spring容器注冊bean

不帶條件注冊bean

我們在PersonConfig2類中新增person01()方法和person02()方法,并為兩個方法添加@Bean注解,如下所示。

@Bean("binghe001")

public Person person01(){

return new Person("binghe001", 18);           

@Bean("binghe002")

public Person person02(){

return new Person("binghe002", 20);           

那麼,這兩個bean預設是否會被注冊到Spring容器中呢,我們建立一個測試用例來測試一下。在SpringBeanTest類中建立testAnnotationConfig6()方法,如下所示。

@Test

public void testAnnotationConfig6(){

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);           

我們運作testAnnotationConfig6()方法,輸出的結果資訊如下所示。

person

binghe001

binghe002

從輸出結果可以看出,同時輸出了binghe001和binghe002。說明預設情況下,Spring容器會将單執行個體并且非懶加載的bean注冊到IOC容器中。

接下來,我們再輸出bean的名稱和bean執行個體對象資訊,此時我們在testAnnotationConfig6()方法中添加相應的代碼片段,如下所示。

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);

Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);           

再次運作SpringBeanTest類中的testAnnotationConfig6()方法,輸出結果如下所示。

給容器中添加Person....

{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}

可以看到,輸出了注冊到容器的bean。

帶條件注冊bean

現在,我們就要提出新的需求了,比如,如果目前作業系統是Windows作業系統,則向Spring容器中注冊binghe001;如果目前作業系統是Linux作業系統,則向Spring容器中注冊binghe002。此時,我們就需要使用@Conditional注解了。

這裡,有小夥伴可能會問:如何擷取作業系統的類型呢,别急,這個問題很簡單,我們繼續向下看。

使用Spring的ApplicationContext接口就能夠擷取到目前作業系統的類型,如下所示。

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);

Environment environment = context.getEnvironment();

String osName = environment.getProperty("os.name");

System.out.println(osName);

我們将上述代碼整合到SpringBeanTest類中的testAnnotationConfig6()方法中,如下所示。

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);

String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);

Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);           

接下來,我們運作SpringBeanTest類中的testAnnotationConfig6()方法,輸出的結果資訊如下所示。

Windows 10

由于我使用的作業系統是Windows 10作業系統,是以在結果資訊中輸出了Windows 10。

到這裡,我們成功擷取到了作業系統的類型,接下來,就可以實作:如果目前作業系統是Windows作業系統,則向Spring容器中注冊binghe001;如果目前作業系統是Linux作業系統,則向Spring容器中注冊binghe002的需求了。此時,我們就需要借助Spring的@Conditional注解來實作了。

要想使用@Conditional注解,我們需要實作Condition接口來為@Conditional注解設定條件,是以,這裡,我們建立了兩個實作Condition接口的類,分别為WindowsCondition和LinuxCondition,如下所示。

WindowsCondition

package io.mykit.spring.plugins.register.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.context.annotation.Condition;

import org.springframework.context.annotation.ConditionContext;

import org.springframework.core.env.Environment;

/**

  • @author binghe
  • @version 1.0.0
  • @description Windows條件,判斷作業系統是否是Windows

    */

public class WindowsCondition implements Condition {

/**
 * ConditionContext:判斷條件使用的上下文環境
 * AnnotatedTypeMetadata:注釋資訊
 */
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    //判斷是否是Linux系統
    //1.擷取到IOC容器使用的BeanFactory
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //2.擷取類加載器
    ClassLoader classLoader = context.getClassLoader();
    //3.擷取目前的環境資訊
    Environment environment = context.getEnvironment();
    //4.擷取bean定義的注冊類,我們可以通過BeanDefinitionRegistry對象檢視
    //Spring容器中注冊了哪些bean,也可以通過BeanDefinitionRegistry對象向
    //Spring容器中注冊bean,移除bean,檢視bean的定義,檢視是否包含某個bean的定義
    BeanDefinitionRegistry registry = context.getRegistry();
    String property = environment.getProperty("os.name");
    return property.contains("Windows");
}           

LinuxCondition

  • @description Linux條件,判斷作業系統是否是Linux

public class LinuxCondition implements Condition {

/**
 * ConditionContext:判斷條件使用的上下文環境
 * AnnotatedTypeMetadata:注釋資訊
 */
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    //判斷是否是Linux系統
    //1.擷取到IOC容器使用的BeanFactory
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //2.擷取類加載器
    ClassLoader classLoader = context.getClassLoader();
    //3.擷取目前的環境資訊
    Environment environment = context.getEnvironment();
    //4.擷取bean定義的注冊類,我們可以通過BeanDefinitionRegistry對象檢視
    //Spring容器中注冊了哪些bean,也可以通過BeanDefinitionRegistry對象向
    //Spring容器中注冊bean,移除bean,檢視bean的定義,檢視是否包含某個bean的定義
    BeanDefinitionRegistry registry = context.getRegistry();
    String property = environment.getProperty("os.name");
    return property.contains("linux");
}           

接下來,我們就需要在PersonConfig2類中使用@Conditional注解添加條件了。添加注解後的方法如下所示。

@Conditional({WindowsCondition.class})

return new Person("binghe001", 18);           

@Conditional({LinuxCondition.class})

return new Person("binghe002", 20);           

此時,我們再次運作SpringBeanTest類中的testAnnotationConfig6()方法,輸出的結果資訊如下所示。

{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18)}

可以看到,輸出結果中不再含有名稱為binghe002的bean了,說明程式中檢測到目前作業系統為Windows10,沒有向Spring容器中注冊名稱為binghe002的bean。

@Conditional注解也可以标注在類上,标注在類上含義為:滿足目前條件,這個類中配置的所有bean注冊才能生效,大家可以自行驗證@Conditional注解标注在類上的情況

@Conditional的擴充注解

@ConditionalOnBean:僅僅在目前上下文中存在某個對象時,才會執行個體化一個Bean。

@ConditionalOnClass:某個class位于類路徑上,才會執行個體化一個Bean。

@ConditionalOnExpression:當表達式為true的時候,才會執行個體化一個Bean。

@ConditionalOnMissingBean:僅僅在目前上下文中不存在某個對象時,才會執行個體化一個Bean。

@ConditionalOnMissingClass:某個class類路徑上不存在的時候,才會執行個體化一個Bean。

@ConditionalOnNotWebApplication:不是web應用,才會執行個體化一個Bean。

@ConditionalOnBean:當容器中有指定Bean的條件下進行執行個體化。

@ConditionalOnMissingBean:當容器裡沒有指定Bean的條件下進行執行個體化。

@ConditionalOnClass:當classpath類路徑下有指定類的條件下進行執行個體化。

@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下進行執行個體化。

@ConditionalOnWebApplication:當項目是一個Web項目時進行執行個體化。

@ConditionalOnNotWebApplication:當項目不是一個Web項目時進行執行個體化。

@ConditionalOnProperty:當指定的屬性有指定的值時進行執行個體化。

@ConditionalOnExpression:基于SpEL表達式的條件判斷。

@ConditionalOnJava:當JVM版本為指定的版本範圍時觸發執行個體化。

@ConditionalOnResource:當類路徑下有指定的資源時觸發執行個體化。

@ConditionalOnJndi:在JNDI存在的條件下觸發執行個體化。

@ConditionalOnSingleCandidate:當指定的Bean在容器中隻有一個,或者有多個但是指定了首選的Bean時觸發執行個體化。

@Conditional 與@Profile 的對比

Spring3.0 也有一些和@Conditional 相似的注解,它們是Spring SPEL 表達式和Spring Profiles 注解 Spring4.0的@Conditional 注解要比@Profile 注解更加進階。@Profile 注解用來加載應用程式的環境。@Profile注解僅限于根據預定義屬性編寫條件檢查。 @Conditional注釋則沒有此限制。

Spring中的@Profile 和 @Conditional 注解用來檢查"If…then…else"的語義。然而,Spring4 @Conditional是@Profile 注解的更通用法。

Spring 3中的 @Profile僅用于編寫基于Environment變量的條件檢查。 配置檔案可用于基于環境加載應用程式配置。

Spring 4 @Conditional注解允許開發人員為條件檢查定義使用者定義的政策。 @Conditional可用于條件bean注冊。

好了,咱們今天就聊到這兒吧!别忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

寫在最後

如果覺得文章對你有點幫助,請微信搜尋并關注「 冰河技術 」微信公衆号,跟冰河學習Spring注解驅動開發。公衆号回複“spring注解”關鍵字,領取Spring注解驅動開發核心知識圖,讓Spring注解驅動開發不再迷茫。

參考:

https://www.cnblogs.com/cxuanBlog/p/10960575.html

原文位址

https://www.cnblogs.com/binghe001/p/13084108.html