【基礎系列】ConfigurationProperties 配置綁定中那些你不知道的事情
在 SpringBoot 項目中,獲取配置屬性可以說是一個非常簡單的事情,將配置寫在aplication.yml
文件之后,我們就可以直接通過@Value
注解來綁定並獲取;此外我們也可以將一個結構化的配置,借助@ConfigurationPorperties
綁定到一個 POJO,然后供項目使用,那么在使用它的時候,不知是否有想過
@ConfigurationPorperties
修飾的類如何生效- 配置參數與定義的 POJO 類型不匹配時會怎樣
- 配置參數的必要性校驗可以怎么支持
- 自定義的配置參數,idea 中如何自動補全
- 已廢棄的參數定義,怎樣友好的提示使用方
- List/Map 格式的參數,怎么使用
- 自定義參數解析規則如何支持
如果上面這些都已經了然於心,那么本文的幫助將不會特別大;如果對此有所疑問,接下來將逐一進行解惑
I. 項目環境
本項目借助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
進行開發
下面是核心的pom.xml
(源碼可以再文末獲取)
<!-- 這個依賴是干嘛的,后文會介紹 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
II. ConfigurationProperties 詳解
1. 配置綁定
假定我們現在自定義一個功能模塊,里面有一些我們自定義的參數,支持通過 yaml 配置文件的方式注入
首先我們可以先定義一個配置類 BindConfig
@Data
@ConfigurationProperties(prefix = "hhui.bind")
public class BindConfig {
private String name;
private Integer age;
private List<String> list;
private Map<String, String> map;
}
請注意上面的注解中,prefix = hhui.bind
,簡單來講就是會讀取配置文件中,前綴為 hhui.bind
的屬性,然后依次賦值到這個類中
BindConfig.name = hhui.bind.name
BindConfig.age = hhui.bind.age
- ...
對應的配置文件如下
hhui:
bind:
name: YiHui
age: 18
list:
- java
- c
- python
map:
wechat: 小灰灰blog
blogs: http://blog.hhui.top
git: http://github.com/liuyueyi
注意事項
- 配置類必須有公共的 Setter 方法,上文中主要是借助 lombok 的
@Data
省略了 Setter 方法的顯示聲明而已 - 類的屬性名與配置文件中的配置名要求匹配
- 大小寫不敏感
- 支持下划線轉駝峰
- 配置類不要求必須是 public
關於上面最后一點,也就表明我們可以在自動 AutoConfiguration 類中,聲明一個內部類來綁定配置信息,如下
@Configuration
@EnableConfigurationProperties({AutoConfiguration.BindConfig.class})
public class AutoConfiguration {
@Data
@ConfigurationProperties(prefix = "hhui.bind")
static class BindConfig {
private String name;
private Integer age;
private List<String> list;
private Map<String, String> map;
}
}
2. 注冊生效
我們通過@ConfigurationProperties
修飾配置類之后,是否直接會生效呢?通常來講,讓它生效有下面三種方式
a. @Component
等注解修飾方式
直接在配置類上添加@Component
, @Configuration
等注解,讓 Spring 容器掃描並加載它
@Data
@Component
@ConfigurationProperties(prefix = "hhui.bind")
public class BindConfig {
}
使用這種方式時,需要注意配置類在自動掃描的包路徑下,否則可能不會被掃描(主要是作為第三方 jar 包提供服務時,可能出現掃描不到的問題)
b. @Bean
注冊
把它當成一個普通的 bean,借助 bean 注冊的方式來實現,也是一個可選的方案,一般的實現方式如下
@Configuration
public class AutoConfiguration {
@Bean
public BindConfig bindConfig() {
return new BindConfig();
}
}
c. @EnableConfigurationProperties
方式
在配置類上,添加這個注解之后,可以實現配置注冊,一般常見的使用姿勢如
@EnableConfigurationProperties({BindConfig.class})
@Configuration
public class AutoConfiguration {
}
d. 小結
上面三種注冊方式,前面兩種的思路是將配置類作為 bean,第三種實現思路和主動注冊 bean 一致(所以想實現主動注冊 bean,可以考慮它的實現邏輯)
3. 參數類型不匹配
如果我們在配置中,一個本來希望接收 int 類型的參數,結果實際上填了一個非整形,會怎樣?
比如前面的配置類,我們實際的配置文件將age
填 18y,來看一下最終會發生什么事情
hhui:
bind:
Name: YiHui
AGE: 18y
list:
- java
- c
- python
map:
wechat: 小灰灰blog
blogs: http://blog.hhui.top
git: http://github.com/liuyueyi
簡單演示,直接在啟動類中測試一下會如何
@SpringBootApplication
public class Application {
public Application(BindConfig config) {
System.out.println(config);
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
參數異常之后,直接啟動失敗,如果對參數的要求沒有那么嚴格,即允許失敗,我們可以通過設置ignoreInvalidFields = true
@Data
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true)
public class BindConfig {
}
再次執行之后,會發現正常啟動,輸出如下
BindConfig(name=YiHui, age=null, list=[java, c, python], map={wechat=小灰灰blog, blogs=http://blog.hhui.top, git=http://github.com/liuyueyi})
注意查看上面的 age,因為傳入的參數非法,所以是 null
說明
結合默認值 + ignoreInvalidFields
方式來支持配置的最大可用性:
- 直接在配置類中,設置屬性的默認值,表示當這個配置不存在或者設置非法時,使用默認的配置
@Data
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true)
public class BindConfig {
private String name;
private Integer age = 18;
private List<String> list;
private Map<String, String> map;
}
再次執行輸出如
BindConfig(name=YiHui, age=18, list=[java, c, python], map={wechat=小灰灰blog, blogs=http://blog.hhui.top, git=http://github.com/liuyueyi}, mainPwd=Pwd(user=一灰灰blog, pwd=yihuihui, code=9))
4. 配置解析規則
常見的配置除了基本類型之外,能嵌套自定義對象么,非基本類型又可以如何解析呢?
a. POJO,List,Map 參數類型
我們新定義一個 Pwd 類
@Data
public class Pwd {
private String user;
private String pwd;
private Integer code;
}
然后擴展一下BindConfig
@Data
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true)
public class BindConfig {
private String name;
private Integer age = 18;
private List<String> list;
private Map<String, String> map;
private Pwd mainPwd;
}
這個時候 mainPwd 對應的 yaml 配置文件可以如下設置
hhui:
bind:
Name: YiHui
AGE: 1h
list:
- java
- c
- python
map:
wechat: 小灰灰blog
blogs: http://blog.hhui.top
git: http://github.com/liuyueyi
# 下面這個對應的是 BindConfg.mainPwd; 可以寫成 main_pwd也可以寫成mainPwd
main_pwd:
user: 一灰灰blog
pwd: yihuihui
code: 9
從上面的介紹也可以看出,對於自定義的 POJO 類是支持的,使用姿勢也沒什么區別
此外,對於 List 和 Map 的使用也給出了實例
b.自定義配置解析
上面我們自定義的Pwd
類,主要借助setter
方法,將匹配的屬性塞入進去;如果我的配置就是一個 json 串,可以注入到一個 POJO 類么
hhui:
bind:
Jwt: '{"token": "11111111123", "timestamp": 1610880489123}'
對應的 Jwt 類如下
@Data
public class Jwt {
private String token;
private Long timestamp;
}
這個時候如想實現上面的配置解析,可以通過實現org.springframework.core.convert.converter.Converter
接口來支持,並通過@ConfigurationPropertiesBinding
注解來表明這是個配置屬性轉換類,不加這個注解會不生效哦
@Component
@ConfigurationPropertiesBinding
public class JwtConverter implements Converter<String, Jwt> {
@Override
public Jwt convert(String source) {
return JSONObject.parseObject(source, Jwt.class);
}
}
說明
使用自定義的配置解析規則時,注意兩點
- 實現接口
Converter
- 使用
@ConfigurationPropertiesBinding
修飾注解
Spring 提供了一些默認的配置解析規則,如
- 文件大小
DataSize
- 對應的 value 可以是 1B, 1KB, 1MB, 1GB...
- 持續時間
Duration
- 對應的 value 可已是 1ns,1us,1ms,1s,1m,1h,1d
5. 配置不存在場景
一個配置類,對應的類中沒有這個屬性會怎樣?
如針對前面的BindConfig
,沒有notExist
這個屬性,但是配置文件中,卻加上了這個
hhui:
bind:
notExist: true
實測之后,發現沒有任何影響,通過查看@ConfigurationProperties
注解的成員,發現可以設置ignoreUnknownFields=false
,從字面上表示出現了未能識別的成員,不會略錯誤,但是在實際測試中,並沒有生效
6. 參數校驗
參數校驗可以說比較常用的 case 了,比如前面的配置age
,基本上不會允許這個參數能是負數,如需要對參數進行校驗,我們可以借助@Validated
來實現校驗
添加 pom 依賴
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
然后再配置類上添加@Validated
,然后就可以在需要校驗的字段上添加對應的限制
@Data
@Validated
@ConfigurationProperties(prefix = "hhui.bind", ignoreInvalidFields = true, ignoreUnknownFields = false)
public class BindConfig {
@Min(13)
@Max(66)
private Integer age = 18;
}
如果我們將 age 參數設置不滿足上面的條件
hhui:
bind:
age: 10
再次測試會發現報如下錯誤
7. IDEA 自動補全提示
平時在 Spring 開發過程中,在 yaml 文件中添加配置時,配合 idea 有非常友好的提示,可以非常友好的補全參數配置
那么我們自定義的參數想實現這個效果應該怎么做呢?
添加文章最開頭的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
添加上面的依賴之后,打包mvn clean package
,然后會發現在 META-INF 下面有個spring-configuration-metadata.json
{
"groups": [
{
"name": "hhui.bind",
"type": "com.git.hui.boot.bind.config.BindConfig",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
}
],
"properties": [
{
"name": "hhui.bind.age",
"type": "java.lang.Integer",
"sourceType": "com.git.hui.boot.bind.config.BindConfig",
"defaultValue": 18
},
{
"name": "hhui.bind.jwt",
"type": "com.git.hui.boot.bind.config.Jwt",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.list",
"type": "java.util.List<java.lang.String>",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.main-pwd",
"type": "com.git.hui.boot.bind.config.Pwd",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.map",
"type": "java.util.Map<java.lang.String,java.lang.String>",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
},
{
"name": "hhui.bind.name",
"type": "java.lang.String",
"sourceType": "com.git.hui.boot.bind.config.BindConfig"
}
],
"hints": []
}
然后自動補全就有了
說明
idea 推薦添加插件Spring Assistant
,支持非常友好的配置注入
8.小結
本文介紹了@ConfigurationProperties
修飾 POJO 類,實現配置的綁定,可以通過將這個類聲明為一個普通 bean 的方式進行注冊,也可以借助@EnableConfigurationProperties
來注冊
在配置參數時,需要注意如果參數類型不一致,會導致項目啟動失敗;可以通過設置ConfigurationProperties#ignoreInvalidFields = true
,來避免這種場景
通過實現接口Converter
+ @ConfigurationPropertiesBinding
來自定義參數解析轉換規則,可以實現各路姿勢的參數解析
配置的自動提示支持也比較簡單,添加org.springframework.boot:spring-boot-configuration-processor
依賴,打包之后在 META-INF 中會多一個 json 文件spring-configuration-metadata.json
II. 其他
0. 項目
項目源碼
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 源碼: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/002-properties-bind
系列博文
- 【基礎系列】實現一個自定義配置加載器(應用篇)
- 【基礎系列】SpringBoot 配置信息之默認配置
- 【基礎系列】SpringBoot 配置信息之配置刷新
- 【基礎系列】SpringBoot 基礎篇配置信息之自定義配置指定與配置內引用
- 【基礎系列】SpringBoot 基礎篇配置信息之多環境配置信息
- 【基礎系列】SpringBoot 基礎篇配置信息之如何讀取配置信息
1. 一灰灰 Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰 Blog 個人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 專題博客 http://spring.hhui.top
- 微信公眾號: 一灰灰blog