Spring生態研習【五】:Springboot中bean的條件注入

在springboot中,開發的確變的簡單了不少,可是,開發者如今但願開發傻瓜式的方便搞定項目中的各類奇怪的需求最好了,不用燒腦,原本程序猿的生活就是枯燥的,不要再給本身添加更多的煩惱。java

 

今天,就爲了方便這點,介紹下,如何解決在開發過程當中,一些場景下,爲了實現一個配置模塊中,基於開關量或者選擇配置項,實現不一樣功能,例如,在一個session共享模塊當中,解決session是基於header傳遞仍是基於cookie傳遞這兩種應用場景,有些應用中但願基於header傳遞sessionId,可是有些應用中但願基於cookie傳遞sessionId,而後,session共享模塊,是一個很是基礎的組件,差很少是一個開箱即用的功能塊。因此呢,最好能配置好,而後只須要基於配置文件中的某個選項,就能實現運行在不一樣的工做模式下。這個可否作到呢?真的只須要改一下配置中的開關量就能實現嗎?spring

 

可否實現,這裏賣個關子,先不說,介紹完了本篇博文後,細心的讀者必定知道答案,或者說必定能明白可否作,怎麼作!express

 

第一大點:先介紹一下springboot中可以支持的或者說封裝好的經常使用的條件注入的註解windows

1 @ConditionalOnBean數組

1.1 基本使用案例springboot

@Component
@ConditionalOnBean(name="aBean")
public class BBean {
  private final ABean aBean;
  public BBean(ABean aBean) {
      // ...
  }
}

 

1.2 使用說明cookie

只有當beang的名稱爲aBean存在的時候,纔會注入BBean。session

 

2 @ConditionalOnMissingBeanapp

2.1 基本案例ide

@Bean
@ConditionalOnMissingBean(name = "notExistsBean")
public BeanToCreate createOneBean() {
    return new BeanToCreate("notExistsBean");
}

 

2.2 使用說明

只有當bean名稱爲notExistsBean不存在的時候,BeanToCreate類型的bean纔會被建立,和@ConditionalOnBean的使用方式相反

 

3 @ConditionalOnClass

3.1 基本使用案例

@Bean
@ConditionalOnClass(DependedClz.class)
public InjectIfClzExists injectIfClzExists() {
    return new InjectIfClzExists("dependedClz");
}

 

3.2 使用說明

只有當Class爲DependedClz.class存在的時候,纔會注入類型爲InjectIfClzExists的bean,使用上和@ConditionalOnBean有些相似。

 

4 @ConditionalOnMissingClass

4.1 使用案例

@Bean
@ConditionalOnMissingClass("com.shihuc.bean.clz.DependedClz")
public InjectIfClzNotExists injectIfClzNotExists() {
    return new InjectIfClzNotExists("com.shihuc.bean.clz.DependedClz");
}

 

4.2 使用說明

只有當類com.shihuc.bean.clz.DependedClz不存在的時候,纔會注入類型爲InjectIfClzNotExists的bean。

 

5 @ConditionalOnProperty

5.1 基本使用案例

springboot的項目中配置文件application.properties文件中有以下配置:

#.....
section.condition_field=noti
section.condition_property=test
#...
@Bean
@ConditionalOnProperty("section.condition_field")
public PropertyExistBean propertyExistBean() {
    return new PropertyExistBean("section.condition_field");
}

 

5.2 使用說明

主要是根據配置文件中的參數,來決定是否須要建立這個bean,這樣就給了咱們一個根據配置來控制Bean的選擇的手段了,這個很是的好用。由於application.properties文件中存在section.condition_field這個屬性,因此,PropertyExistBean這個bean會被建立出來。

 

5.3 擴展用法

5.3.1 註解定義

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

    /**
     * Alias for {@link #name()}.
     * @return the names
     * 注意,這個value和name不能同時使用
     */
    String[] value() default {};

    /**
     * A prefix that should be applied to each property. The prefix automatically ends
     * with a dot if not specified.
     * @return the prefix
     */
    String prefix() default "";

    /**
     * The name of the properties to test. If a prefix has been defined, it is applied to
     * compute the full key of each property. For instance if the prefix is
     * {@code app.config} and one value is {@code my-value}, the full key would be
     * {@code app.config.my-value}
     * <p>
     * Use the dashed notation to specify each property, that is all lower case with a "-"
     * to separate words (e.g. {@code my-long-property}).
     * @return the names
     */
    String[] name() default {};

    /**
     * The string representation of the expected value for the properties. If not
     * specified, the property must <strong>not</strong> be equal to {@code false}.
     * @return the expected value
     */
    String havingValue() default "";

    /**
     * Specify if the condition should match if the property is not set. Defaults to
     * {@code false}.
     * @return if should match if the property is missing
     */
    boolean matchIfMissing() default false;
}

 

當我想實現配置文件中存在屬性aaa.bbb且其屬性的值爲ccc時,才注入bean實例DDDD(名爲dddd)。

@Bean("dddd")
@ConditionalOnProperty(value="aaa.bbbb", havingValue="ccc")
public DDDD propertyExistBean() {
    return new DDDD("aaa.bbb");
}

 

6 @ConditionalOnExpression

6.1 使用案例

配置文件application.properties中存在下面的配置內容:

conditional.flag=true

java對應代碼:

@Bean
@ConditionalOnExpression("#{'true'.equals(environment['conditional.flag'])}")
public ExpressTrueBean expressTrueBean() {
    return new ExpressTrueBean("express true");
}

 

6.2 使用說明

相比較前面的Bean,Class是否存在,配置參數property是否存在或者有某個值而言,這個依賴SPEL表達式的,使用起來就功能顯得更增強大了;其主要就是執行Spel表達式,根據返回的true/false來判斷是否知足條件。

 

第二大點: spring基於Condition接口和@Conditional註解進行注入bean

這個至關因而條件注入bean的根源解決方案,上述其餘幾個ConditionalOnXXXX的註解,都是這個Conditional註解的具體場景的定製版,假如沒有可以知足本身的應用場景的,或者說要本身實現一個比較特殊的條件注入呢,例如多個條件同時成立之類,怎麼辦呢,那就須要經過實現Condition接口而後基於@Conditional註解進行使用了。

 

1 @Conditional註解定義

//此註解能夠標註在類和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    /**
     * All {@link Condition}s that must {@linkplain Condition#matches match}
     * in order for the component to be registered.
     */
    Class<? extends Condition>[] value();
}

注意,這個註解就一個參數value,且入參是一個Condition的Class的數組。

 

2 Condition是什麼?

@FunctionalInterface
public interface Condition {

    /**
     * Determine if the condition matches.
     * @param context the condition context
     * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     * or {@link org.springframework.core.type.MethodMetadata method} being checked
     * @return {@code true} if the condition matches and the component can be registered,
     * or {@code false} to veto the annotated component's registration
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

 

3. 使用案例

假設屬性配置文件中,有兩個環境參數,一個是溫度temp,一個是溼度humi,只有當溫度高於30度,且溼度大於50個點時,啓用Linux,當溫度小於30度且溼度小於50個點時,啓用Windows,這個只是爲了說明在一個@Conditional裏面將多個條件知足該如何實現,還有其餘的業務場景,能夠參照這個案例。

3.1 配置文件參數

#溫度數據,攝氏溫度
conditional.prop.temp=29
#溼度數據,百分比,這裏不帶百分號,至關於擴大100倍,使用的時候除以100
conditional.prop.humi=51

 

3.2 定義bean

有一個HeWoBean的接口,以及兩個實現類HelloBean和WorldBean。

/**
 * @Author: chengsh05
 * @Date: 2019/8/29 16:17
 */
public interface HeWoBean {

    public String toString();
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 15:52
 */
public class HelloBean implements HeWoBean {

    public String getHhh() {
        return hhh;
    }

    public void setHhh(String hhh) {
        this.hhh = hhh;
    }

    public String getEee() {
        return eee;
    }

    public void setEee(String eee) {
        this.eee = eee;
    }

    String hhh;

    String eee;

    public HelloBean(String hh, String ee) {
        this.hhh = hh;
        this.eee = ee;
    }

    @Override
    public String toString() {
        return this.hhh + ", " + this.eee;
    }
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 15:54
 */
public class WorldBean implements HeWoBean {
    public String getWww() {
        return www;
    }

    public void setWww(String www) {
        this.www = www;
    }

    public String getOoo() {
        return ooo;
    }

    public void setOoo(String ooo) {
        this.ooo = ooo;
    }

    String www;
    String ooo;

    public WorldBean(String ww, String oo) {
        this.www = ww;
        this.ooo = oo;
    }

    @Override
    public String toString() {
        return this.www + ", " + this.ooo;
    }
}

 

3. condition接口實現類及@Conditional應用

/**
 * @Author: chengsh05
 * @Date: 2019/8/29 9:08
 * @Description: 配置信息中,溫度和溼度條件知足的時候,即當溫度temp大於30度,溼度大於50%,啓用Linux
 */
public class LinuxTime implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String tempStr = context.getEnvironment().getProperty("conditional.prop.temp");
        float temp = Float.valueOf(tempStr);
        String humiStr = context.getEnvironment().getProperty("conditional.prop.humi");
        float humi = Float.valueOf(humiStr);
        if(temp > 30 && humi > 60){
            return true;
        }
        return false;
    }
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 9:07
 * @Description: 配置信息中,溫度和溼度條件知足的時候,即當溫度temp小於30度,溼度小於50%,啓用windows
 */
public class WindowsTime implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String tempStr = context.getEnvironment().getProperty("conditional.prop.temp");
        float temp = Float.valueOf(tempStr);
        String humiStr = context.getEnvironment().getProperty("conditional.prop.humi");
        float humi = Float.valueOf(humiStr);
        if(temp < 30 && humi < 60){
            return true;
        }
        return false;
    }
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 15:50
 */
@Configuration
public class MyConditional {

    @Bean("mybean")
    @Conditional(LinuxTime.class)
    public HelloBean createHello() {
        return new HelloBean("hello", "Linux");
    }

    @Bean("mybean")
    @Conditional(WindowsTime.class)
    public WorldBean createWorld() {
        return new WorldBean("world", "Windows");
    }
}

 

4.應用驗證

/**
 * @Author: chengsh05
 * @Date: 2019/8/29 16:03
 */
@Controller
@RequestMapping("/condition")
public class ConditionalController {

    @Autowired
    @Qualifier("mybean")
    private HeWoBean myBean;

    @RequestMapping("/check")
    @ResponseBody
    public void check() {
        System.out.println("///||||\\\\ ==> " + myBean.toString());
    }
}

分析來看,LinuxTime由於沒有知足溫度temp和溼度humi的條件(即在配置文件中的參數),因此,LinuxTime這個bean在MyConditional這個配置類中是不會被建立出來的,即最終HeHoBean這個就只有WorldBean被注入到spring容器了。打印的日誌,也證明了這個。

///||||\\ ==> world, Windows

 

 

總結:

1. 基於springboot內置的條件註解,開發一些應用,基於某種條件進行bean的注入仍是很方便的,基本能夠解決大部分常見場景需求。

2. 基於內置的條件注入註解的組合使用,能夠實現多條件約束的bean的注入需求,只有多個條件注入條件都成立時,對應的bean纔會被注入到spring的容器。

3. 內置註解無論單獨用仍是組合使用,都不能搞定你的應用需求,那麼能夠選擇實現condition接口,基於@Conditional註解來本身完成條件注入的需求了。

 

到這裏,看官們,你是否有結論了,關於前面提到的,session共享模塊,基於配置參數開關量,靈活切換模塊工做在header模式仍是cookie模式?答案是能夠的,至於如何實現,結合我這裏的介紹,是能獲得答案的。

相關文章
相關標籤/搜索