爲了控制Bean的加載我使出了這些殺手鐗

故事一:絕代有佳人,幽居在空谷
美女同窗小張,在工做中遇到了煩心事。心情那是破涼破涼的,沒法言喻。
故事背景是最近因爲需求變更,小張在項目中加入了 MQ 的集成,剛開始還沒什麼問題,後面慢慢問題的顯露出來了。
本身在本地 Debug 的時候老是能消費到消息,因爲歷史緣由,公司的項目只區分了兩套環境,也就是測試和線上。本地啓動默認就是測試環境,因此會消費測試環境的消息。
MQ 的配置代碼以下:web

@Configuration
public class MqConfig {
    @Bean(initMethod = "start", destroyMethod = "shutdown")
    public ConsumerBean consumerBean() {
        // ....
    }
}

想要解決小張的問題,那麼就必須得有第三個環境的區分,也就是增長一個本地開發環境,而後經過環境來決定是否須要初始化 MQ。
這個時候就能夠用到 Spring Boot 爲咱們提供的 Conditional 家族的註解了,@Conditional 註解會根據具體的條件決定是否建立 bean 到容器中, 以下圖:
爲了控制Bean的加載我使出了這些殺手鐗
經過@ConditionalOnProperty 來決定 MqConfig 是否要加載,@ConditionalOnProperty 的 name 就是配置項的名稱,havingValue 就是匹配的值,也就是在 application 配置中存在 env=dev 纔會初始化 MqConfig。代碼以下:spring

@Configuration
@ConditionalOnProperty(name = "env", havingValue = "dev")
public class MqConfig {
    @Bean(initMethod = "start", destroyMethod = "shutdown")
    public ConsumerBean consumerBean() {
        // ....
    }
}

但這好像不符合小張同窗的需求呀,需求是 dev 環境不加載纔對。還有一個就是歷史緣由,增長一個環境有風險,由於對應的環境加載的內容什麼的,都須要有變更,因此仍是保留歷史狀況,環境不變,看能不能從其餘的點解決這個問題。
如今面臨的問題是不能增長新的環境,保留以前的 test 和 prod。只須要在 test 和 prod 初始化 Mq。br/>方案一:@ConditionalOnProperty
仍是堅持使用@ConditionalOnProperty,既然不能經過環境來,咱們能夠單獨增長一個屬性來決定是否要啓用 Mq, 好比定義爲:mq.enabled=true 表示開啓,mq.enabled=false 表示不開啓。
而後在 test 和 prod 啓動的時候增長-Dmq.enabled=true 或者在對應的配置文件中增長也能夠,本地開發的時候-Dmq.enabled=false 就能夠了。
雖然可以解決問題,可是不是最佳的方案,由於已有的環境和開發人員本地都得增長啓動參數。
方案二:繼承 SpringBootCondition 自定義條件
可使用@Conditional(MqConditional.class)註解,自定義一個條件類,在類中去判斷是否要加載 bean。app

public class MqConditional extends SpringBootCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String env = environment.getProperty("env");
        if (StringUtils.isBlank(env)) {
            return ConditionOutcome.noMatch("no match");
        }
        if (env.equals("test") || env.equals("prod")) {
            return ConditionOutcome.match();
        }
        return ConditionOutcome.noMatch("no match");
    }
}

方案三:繼承 AnyNestedCondition 自定義條件
可使用@Conditional(MqAvailableCondition.class)註解,自定義一個條件類,在類中可使用其餘的 Conditional 註解來進行判斷,好比使用@ConditionalOnProperty。框架

@Order(Ordered.LOWEST_PRECEDENCE)
public class MqAvailableCondition extends AnyNestedCondition {
    public MqAvailableCondition() {
        super(ConfigurationPhase.REGISTER_BEAN);
    }
    @ConditionalOnProperty(name = "env", havingValue = "test")
    static class EnvTest {
    }
    @ConditionalOnProperty(name = "env", havingValue = "prod")
    static class EnvProd {
    }
}

方案四:@ConditionalOnExpression
支持 SpEL 進行判斷,若是知足 SpEL 表達式條件則加載這個 bean。這個就至關靈活了,能夠將須要知足的條件都寫進來。
@ConditionalOnExpression("#{'test'.equals(environment['env']) || 'prod'.equals(environment['env'])}")ide

上面的表達式定義了 Spring Environment 中只要有 env 爲 test 或者 prod 的時候就會初始化 MqConfig。這樣一來老的啓動命令都不用改變,本地開發的時候也不用增長參數,能夠說是最佳的方案,由於改動的點變少了,出錯的概率小,使用難度低。
故事二:北方有佳人,絕世而獨立
美女小楊同窗最近也遇到了煩心事,雖然是女生,可是也工做了幾年了。最近受到領導重用,讓她搭一套 Spring Cloud 的框架給同事們分享一下。
她有個想法是將某些信息能夠經過 Feign 或者 RestTemplate 進行傳遞,自然友好的方式就是在攔截器中統一實現。
若是在每一個服務中都寫一份同樣的代碼,就顯得很低級了,因此她將這兩個攔截器統一寫在一個模塊中,做爲 Spring Boot Starter 的方式引入。
問題一
遇到的第一個問題是這個模塊引入了 Feign 和 spring-web 兩個依賴,想作的通用一點,就是使用者可能會用 Feign 來調用接口,也可能會用 RestTemplate 來調用接口,若是使用者不用 Feign, 可是引入了這個 Starter 也會依賴 Feign。
因此須要在依賴的時候設置 Feign 的 Maven 依賴 optional=true,讓使用者本身去引入依賴。學習

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
  <optional>true</optional>
</dependency>

問題二
第二個問題是攔截器的初始化,若是不作任何處理的話兩個攔截器都會被初始化,若是使用者沒有依賴 Feign,那麼就會報錯,因此咱們須要對攔截器的初始化進行處理。
下面是默認的配置:測試

@Bean
public FeignRequestInterceptor feignRequestInterceptor() {
  return new FeignRequestInterceptor();
}
@Bean
public RestTemplateRequestInterceptor restTemplateRequestInterceptor() {
  return new RestTemplateRequestInterceptor();
}

兩個攔截器都是實現框架自帶的接口,因此咱們能夠在最外層使用@ConditionalOnClass 來判斷若是項目中存在這個 Class 再裝置配置。
第二層能夠經過@ConditionalOnProperty 來決定是否要啓用,將控制權交給使用者。rest

@Configuration
@ConditionalOnClass(name = "feign.RequestInterceptor")
protected static class FeignRequestInterceptorConfiguration {
    @Bean
    @ConditionalOnProperty("feign.requestInterceptor.enabled")
    public FeignRequestInterceptor feignRequestInterceptor() {
      return new FeignRequestInterceptor();
    }
}
@Configuration
@ConditionalOnClass(name = "org.springframework.http.client.ClientHttpRequestInterceptor")
protected static class RestTemplateRequestInterceptorConfiguration {

    @Bean
    @ConditionalOnProperty("restTemplate.requestInterceptor.enabled")
    public RestTemplateRequestInterceptor restTemplateRequestInterceptor() {
      return new RestTemplateRequestInterceptor();
    }

}

故事三:本身去學習
文章裏只根據案例講了一個使用的方式,固然還有不少沒有講的,你們能夠本身去嘗試瞭解一些做用以及在什麼場景可使用,像@ConditionalOnBean,@ConditionalOnMissingBean 等註解。
另外一種學習的方式就是鼓勵你們去看一些框架的源碼,特別在 Spring Cloud 這些框架中大量的自動配置,都有用到這些註解,我貼幾個圖給你們看看。code

爲了控制Bean的加載我使出了這些殺手鐗
爲了控制Bean的加載我使出了這些殺手鐗
熱文推薦
農村小夥艱難的復工之路blog

這位RD同窗,你好像對JD有點誤解!

好機會,我要幫女同事解決Maven衝突問題

上線前一個小時,dubbo這個問題可把我折騰慘了

爲了控制Bean的加載我使出了這些殺手鐗
爲了控制Bean的加載我使出了這些殺手鐗
爲了控制Bean的加載我使出了這些殺手鐗
若有收穫,點個在看,誠摯感謝

尹吉歡我不差錢啊

相關文章
相關標籤/搜索