@ConfigurationProperties 到底接受哪些写法?列表和 Map 在多个来源里怎么合并?嵌套对象会不会被校验?只有一个带参构造器时 setter 还起不起作用?这篇在 Spring Boot 4.0.8 上逐个场景实测。
application.properties 里写 app.first-name=v:
firstName=v, maxConnections=0, hosts=[], labels={}
application.properties 里写 app.firstName=v:
firstName=v, maxConnections=0, hosts=[], labels={}
application.properties 里写 app.first_name=v:
firstName=v, maxConnections=0, hosts=[], labels={}
application.properties 里写 app.firstname=v:
firstName=v, maxConnections=0, hosts=[], labels={}
application.properties 里写 app.FIRST-NAME=v:
firstName=v, maxConnections=0, hosts=[], labels={}
操作系统环境变量 APP_FIRSTNAME=v:
firstName=v, maxConnections=0, hosts=[], labels={}
操作系统环境变量 APP_FIRST_NAME=v:
firstName=v, maxConnections=0, hosts=[], labels={}
操作系统环境变量 APP_FIRST__NAME=v:
firstName=null, maxConnections=0, hosts=[], labels={}properties 里 5 种写法、环境变量里 2 种写法都能绑定;环境变量里多一个下划线(APP_FIRST__NAME)就绑定不上。
app.hosts=a,b,c:
firstName=null, maxConnections=0, hosts=[a, b, c], labels={}
app.hosts[0]=x 与 app.hosts[1]=y:
firstName=null, maxConnections=0, hosts=[x, y], labels={}
app.hosts[1]=y(没有 [0]):
启动失败:Description: Binding to target [Bindable@25d3cfc8 type = java.util.List<java.lang.String>, value = 'provided', annotations = array<Annotation>[[empty]], bindMethod = [null]] failed: Property: app.hosts[1] Value: "y" Origin: … Reason: The elements [app.hosts[1]] were left unbound. Action: Update your application's configuration
环境变量 APP_HOSTS_0=e0、APP_HOSTS_1=e1:
firstName=null, maxConnections=0, hosts=[e0, e1], labels={}
环境变量 APP_HOSTS=a,b:
firstName=null, maxConnections=0, hosts=[a, b], labels={}
文件里 app.hosts=a,b,c,系统属性 -Dapp.hosts[0]=z(不同来源的列表合并还是替换):
firstName=null, maxConnections=0, hosts=[z], labels={}
app.labels.env=prod:
firstName=null, maxConnections=0, hosts=[], labels={env=prod}
app.labels.a.b=x(key 里有点号,不加方括号):
firstName=null, maxConnections=0, hosts=[], labels={a.b=x}
app.labels[a.b]=x(加方括号):
firstName=null, maxConnections=0, hosts=[], labels={a.b=x}
环境变量 APP_LABELS_ENV=prod:
firstName=null, maxConnections=0, hosts=[], labels={env=prod}
文件里 app.labels.a=1,系统属性 -Dapp.labels.b=2(不同来源的 Map 合并还是替换):
firstName=null, maxConnections=0, hosts=[], labels={b=2, a=1}不同来源的列表整体替换,Map 按 key 合并;下标必须从 [0] 连续;key 里的点号可以用方括号括起来。
Duration(没有单位的数字默认毫秒)
"30s" → PT30S
"PT30S" → PT30S
"2m" → PT2M
"500" → PT0.5S
"1.5s" → 失败:'1.5s' is not a valid duration
"30 s" → 失败:'30 s' is not a valid duration
"1d" → PT24H
"-5s" → PT-5S
"PT-5S" → PT-5S
"30seconds" → 失败:'30seconds' is not a valid duration
Duration(@DurationUnit(SECONDS))
"500" → PT8M20S
"500ms" → PT0.5S
"2m" → PT2M
DataSize
"10MB" → 10485760B
"10mb" → 失败:Unknown data unit suffix 'mb'
"1024" → 1024B
"1KB" → 1024B
"1.5MB" → 失败:'1.5MB' does not match data size pattern
"10 MB" → 10485760B
"2GB" → 2147483648B
boolean
"true" → true
"TRUE" → true
"yes" → true
"on" → true
"1" → true
"y" → 失败:Invalid boolean value 'y'
"no" → false
"off" → false
"0" → false
"t" → 失败:Invalid boolean value 't'
"" → null
枚举 Mode{FAST_MODE,SLOW_MODE}
"FAST_MODE" → FAST_MODE
"fast-mode" → FAST_MODE
"fast_mode" → FAST_MODE
"fastMode" → FAST_MODE
"FAST-MODE" → FAST_MODE
"fast mode" → FAST_MODE
"fastmode" → FAST_MODE
"Fast_Mode" → FAST_MODE
int
"42" → 42
"042" → 42
"0x1F" → 31
"1,000" → 失败:For input string: "1,000"
"3.0" → 失败:For input string: "3.0"
"1000L" → 失败:For input string: "1000L"
"" → null
List<String>(逗号分隔)
"a,b,c" → [a, b, c]
"a, b ,c" → [a, b, c]
"a,,c" → [a, , c]
"a" → [a]
"" → []
"a;b" → [a;b]
-- 绑定失败时的启动报错
app.max-connections=abc:
启动失败:Description: Failed to bind properties under 'app.max-connections' to int: Property: app.max-connections Value: "abc" Origin: … Reason: failed to convert java.lang.String to int (caused by java.lang.NumberFormatException: For input string: "abc") Action: Update your application's configurationDuration 无单位按毫秒、不接受 1.5s;DataSize 单位要大写、不接受小数;boolean 接受 yes/on/1,不接受 y/t;枚举写法宽松。
name 空白、pool=11,inner.url 与 inner2.url 都填了:
启动失败:Description: Binding to target demo.V failed: Property: v.name Value: "" Origin: … Reason: must not be blank Property: v.pool Value: "11" Origin: … Reason: must be less than or equal to 10 Action: Update your application's configuration
name 与 pool 正常,inner.url(嵌套对象,没有 @Valid)缺失,inner2.url 填了:
V{name=ok, pool=5, inner.url=null, inner2.url=u2}
name 与 pool 正常,inner2.url(嵌套对象,有 @Valid)缺失,inner.url 填了:
启动失败:Description: Binding to target demo.V failed: Property: v.inner2.url Value: "null" Reason: must not be blank Action: Update your application's configuration
类上没有 @Validated,字段上写了 @NotBlank,name 缺失:
NoVal{name=null}(类上没有 @Validated)
类上有 @Validated,但运行时 classpath 里没有 hibernate-validator(只有 spring-boot-starter),name 空白、pool=11:
V{name=, pool=11, inner.url=null, inner2.url=null}需要 @Validated 加 classpath 里的校验实现;嵌套对象要在字段上加 @Valid;没有校验器时不报错也不校验。
字段是 final、只有 getter 没有 setter,配置 g.name=changed:
启动失败:Description: Failed to bind properties under 'g' to demo.Getters: Property: g.name Value: "changed" Origin: … Reason: java.lang.IllegalStateException: No setter found for property: name Action: Update your application's configuration
字段不是 final、只有 getter 没有 setter,配置 g.count=7:
启动失败:Description: Failed to bind properties under 'g' to demo.Getters: Property: g.count Value: "7" Origin: … Reason: java.lang.IllegalStateException: No setter found for property: count Action: Update your application's configuration
同样的类,g.name、g.count 都不配:
Getters{name=init, count=1}(两个字段都只有 getter,没有 setter)
record,什么都不配:
Rec[name=null, count=0, tags=null, m=null, in=null, inDef=Inner[url=null, port=8080], withDef=dflt]
record,配置 rec.name=n、rec.count=3、rec.tags=x,y、rec.m.k=v、rec.in.url=u、rec.in-def.url=u2、rec.with-def=given:
Rec[name=n, count=3, tags=[x, y], m={k=v}, in=Inner[url=u, port=8080], inDef=Inner[url=u2, port=8080], withDef=given]
类有两个构造器、都没有 @ConstructorBinding,配置 multi.a=1:
启动失败(没有 FailureAnalyzer 报告,堆栈里的异常):Caused by: java.lang.NoSuchMethodException: demo.Multi.<init>()
类有两个构造器、其中一个标了 @ConstructorBinding,配置 multi2.a=1、multi2.b=2:
Multi2{a=1, b=2}
类只有一个带参构造器(没有注解)又有 setter,配置 both.a=1、both.b=2:
Both{a=1, b=field-default}只有 getter 的字段配了值会启动失败;record 的嵌套对象与集合缺失时是 null;多个构造器要标 @ConstructorBinding;单个带参构造器会让 setter 属性不再绑定。
app.first-name=initial 启动,启动之后再往 Environment 里加新值:
firstName=initial, maxConnections=0, hosts=[], labels={}
启动之后在 Environment 最前面加 app.first-name=changed:
Environment.getProperty = changed
@ConfigurationProperties bean = initial
@Value 字段 = initial
Binder.get(env).bind(...) 重新绑定 = changedEnvironment 之后变了,已绑定的 bean 和 @Value 字段不会更新,需要重新绑定。