在開發Spring Boot應用時會用到根據條件來向Spring IoC容器注入Bean。好比配置文件存在了某個配置屬性才注入Bean :java
圖中紅色的部分是說,只有ali.pay.v1.app-id
存在於Spring的環境配置中時這個@Configuration
標記的類才能注入Spring IoC。git
這裏面的@ConditionalOnProperty
就是條件註解系列的一種。它還有不少種來知足各類場景的條件註解:spring
其實數量遠不止截圖中這幾個,在Spring 家族的其它框架中也有實現。數據結構
這裏扯得有點遠了,今天不是來說這些條件控制註解的用法的,只是我發現了一個使用條件註解@ConditionalOnProperty
沒法解決的問題。app
條件注入參考往期:Spring Boot 2 實戰:使用 @Condition 註解來根據條件注入 Bean框架
下面是一段配置文件: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
配置裏的 foo
、bar
實際上是做爲Map
中的key
來標識V1
的,和其它配置參數不一樣,這個key
用戶能夠隨意定義一個String
來標識,多是foo
,多是bar
,徹底根據開發者的喜愛進行主觀定義。這個時候你想根據app.v1.*.name
(暫時用通配符*
)來進行@ConditionalOnProperty
判斷是行不通的,由於你不肯定*
的值,該怎麼辦呢?ui
這裏我花了一天的時間去摸索,最開始我認爲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注入。
今天利用Spring Boot 2.0的數據綁定特性解決了一個實際需求,花了很多時間。當咱們解決問題陷入困境時,首先要去想一想有沒有相似場景以及對應的解決方案。這一樣說明平時的積累很重要,不少粉絲的問題其實公衆號都有講過,因此到處留心解釋學問。多多留意:碼農小胖哥,共同窗習,共同進步。
關注公衆號:Felordcn 獲取更多資訊