Spring Boot 2.0 的配置綁定類Bindable竟然如此強大

1. 前言

在開發Spring Boot應用時會用到根據條件來向Spring IoC容器注入Bean。好比配置文件存在了某個配置屬性才注入Beanjava

根據配置屬性來動態注入Bean

圖中紅色的部分是說,只有ali.pay.v1.app-id存在於Spring的環境配置中時這個@Configuration標記的類才能注入Spring IoCgit

這裏面的@ConditionalOnProperty就是條件註解系列的一種。它還有不少種來知足各類場景的條件註解:spring

條件註解家族

其實數量遠不止截圖中這幾個,在Spring 家族的其它框架中也有實現。數據結構

這裏扯得有點遠了,今天不是來說這些條件控制註解的用法的,只是我發現了一個使用條件註解@ConditionalOnProperty沒法解決的問題。app

條件注入參考往期:Spring Boot 2 實戰:使用 @Condition 註解來根據條件注入 Bean框架

2. 配置文件存在Map結構的場景

下面是一段配置文件:ide

app:
 v1:
  foo:
    name: felord.cn
    description: 碼農小胖哥
  bar:
    name: ooxx.cn
    description: xxxxxx

對應配置類:學習

@Data
@ConfigurationProperties("app")
public class AppProperties {
    /**
     *  
     */
    private Map<String, V1> v1 = new HashMap<>();

    /**
     *  
     *
     * @author felord.cn
     * @since 1.0.0.RELEASE
     */
    @Data
    public static class V1 {
        /**
         * name
         */
        private String name;
        /**
         * description
         */
        private String description;

    }
}

特殊之處來了,yml配置裏的 foobar實際上是做爲Map中的key來標識V1的,和其它配置參數不一樣,這個key用戶能夠隨意定義一個String來標識,多是foo,多是bar,徹底根據開發者的喜愛進行主觀定義。這個時候你想根據app.v1.*.name(暫時用通配符*)來進行@ConditionalOnProperty判斷是行不通的,由於你不肯定*的值,該怎麼辦呢?ui

3. 解決方案

這裏我花了一天的時間去摸索,最開始我認爲Spring提供通配符(app.v1.*.name)甚至是SpringEL表達式能夠拿到,可是搞了半天無功而返。code

忽然我想到以前看Spring Security OAuth2源碼中有相似的邏輯。用過Spring Security OAuth2相關的都知道Spring Security OAuth2也要求用戶自定義一個key來標識本身的OAuth2客戶端。好比我用Gitee的:

spring:
  security:
    oauth2:
      client:
        registration:
          gitee:
            client-id: xxxxxx
            client-secret: xxxxx

這裏的key就是gitee,固然這根據開發者心情決定,甚至你用zhangshan做爲key均可以。

Spring Security OAuth2 提供了相關的條件注入思路,下面是其條件注入判斷的核心類:

public class ClientsConfiguredCondition extends SpringBootCondition {

   private static final Bindable<Map<String, OAuth2ClientProperties.Registration>> STRING_REGISTRATION_MAP = Bindable
         .mapOf(String.class, OAuth2ClientProperties.Registration.class);

   @Override
   public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
      ConditionMessage.Builder message = ConditionMessage.forCondition("OAuth2 Clients Configured Condition");
      Map<String, OAuth2ClientProperties.Registration> registrations = getRegistrations(context.getEnvironment());
      if (!registrations.isEmpty()) {
         return ConditionOutcome.match(message.foundExactly("registered clients " + registrations.values().stream()               .map(OAuth2ClientProperties.Registration::getClientId).collect(Collectors.joining(", "))));
      }
      return ConditionOutcome.noMatch(message.notAvailable("registered clients"));
   }

   private Map<String, OAuth2ClientProperties.Registration> getRegistrations(Environment environment) {
      return Binder.get(environment).bind("spring.security.oauth2.client.registration", STRING_REGISTRATION_MAP)
            .orElse(Collections.emptyMap());
   }

}

顯然OAuth2ClientProperties的結構和咱們要驗證的AppProperties結構是同樣的。因此上面的邏輯是能夠抄過來的,它能夠將環境配置中的帶有不肯定key的配置綁定到咱們的配置類AppProperties中。核心的綁定邏輯是這一段:

Binder.get(environment).bind("spring.security.oauth2.client.registration", STRING_REGISTRATION_MAP)

首先經過Bindable來聲明一個可綁定的數據結構,這裏調用了mapOf方法聲明瞭一個Map的數據綁定結構。而後經過綁定的具體操做對象Binder從配置環境接口Environment中提取了spring.security.oauth2.client.registration開頭的配置屬性並注入到Map中去。既然咱們可以獲取到了Map,根據什麼策略判斷就徹底掌握在咱們手中了。

Bindable爲Spring Boot 2.0提供的數據綁定新特性,有興趣可從spring.io獲取更多信息。

接下來不用我說了吧,照葫蘆畫瓢還有誰不會呢?配合@Conditional註解就能實現根據app.v1下參數的實際狀況來動態的進行Bean注入。

4. 總結

今天利用Spring Boot 2.0的數據綁定特性解決了一個實際需求,花了很多時間。當咱們解決問題陷入困境時,首先要去想一想有沒有相似場景以及對應的解決方案。這一樣說明平時的積累很重要,不少粉絲的問題其實公衆號都有講過,因此到處留心解釋學問。多多留意:碼農小胖哥,共同窗習,共同進步。

關注公衆號:Felordcn 獲取更多資訊

我的博客:https://felord.cn

相關文章
相關標籤/搜索