Spring Boot中@ConfigurationProperties嵌套集合绑定异常问题
Spring Boot配置属性绑定:集合元素属性无法跨配置源合并问题
在Spring Boot 2.6.4中,使用@ConfigurationProperties结合@ConstructorBinding的Kotlin数据类时,遇到集合类型字段的元素属性无法跨配置源合并的问题:多个配置源分别配置集合第一个元素的不同属性,但绑定到类实例时,该元素仅能获取单个配置源的值,而嵌套类字段则能正常合并。同时Environment可以正确读取合并后的属性值。
Kotlin配置类
@ConfigurationProperties("tpp.test.root") @ConstructorBinding data class RootPropperties( var rootField1: String = "", var rootField2: String = "", var nested: NestedProperties = NestedProperties(), var nestedList: List<NestedListProperties> = listOf() ) { data class NestedProperties( var nestedField1: String = "", var nestedField2: String = "" ) @ConstructorBinding data class NestedListProperties( var nestedListField1: String = "", var nestedListField2: String = "" ) }
配置文件
application.yml
tpp: test: root: root-field1: default nested: nested-field1: default nested-list: - nested-list-field1: default
application-dev1.yml
tpp: test: root: root-field2: dev1 nested: nested-field2: dev1 nested-list: - nested-list-field2: dev1
测试代码
@ActiveProfiles("dev1") @SpringBootTest internal class ConfigurationPropertiesTest { @Autowired lateinit var environment: Environment @Autowired lateinit var rootPropperties: RootPropperties @Test fun `configuration properties binding`() { Assertions.assertEquals("default", rootPropperties.rootField1) Assertions.assertEquals("dev1", rootPropperties.rootField2) Assertions.assertEquals("default", rootPropperties.nested.nestedField1) Assertions.assertEquals("dev1", rootPropperties.nested.nestedField2) Assertions.assertTrue(rootPropperties.nestedList.isNotEmpty()) // 断言失败:Expected :default,Actual : Assertions.assertEquals("default", rootPropperties.nestedList[0].nestedListField1) Assertions.assertEquals("dev1", rootPropperties.nestedList[0].nestedListField2) } @Test fun `environment binding`() { Assertions.assertEquals("default", environment.getProperty("tpp.test.root.root-field1")) Assertions.assertEquals("dev1", environment.getProperty("tpp.test.root.root-field2")) Assertions.assertEquals("default", environment.getProperty("tpp.test.root.nested.nested-field1")) Assertions.assertEquals("dev1", environment.getProperty("tpp.test.root.nested.nested-field2")) Assertions.assertEquals("default", environment.getProperty("tpp.test.root.nested-list[0].nested-list-field1")) Assertions.assertEquals("dev1", environment.getProperty("tpp.test.root.nested-list[0].nested-list-field2")) } }
问题现象
- 针对
RootPropperties的测试在集合元素属性断言处失败,rootPropperties.nestedList[0].nestedListField1为空值,其余非集合相关断言均通过。 - 针对
Environment的测试全部通过,能正确获取跨配置源合并后的集合元素属性值。
原因及解决方案
原因
使用@ConstructorBinding时,Spring Boot的配置属性绑定逻辑默认会替换整个集合对象,而非合并集合内元素的属性。对于嵌套类这种单个对象,绑定逻辑会合并其属性;但集合元素会被视为独立对象,后加载的配置源中的集合定义会覆盖之前的,导致元素仅保留最后一个配置源的属性设置。
解决方案
移除集合元素类的
@ConstructorBinding
删除NestedListProperties上的@ConstructorBinding注解,改用默认的setter绑定方式,这样Spring Boot会对集合中的元素进行属性合并,而非替换整个元素:data class NestedListProperties( var nestedListField1: String = "", var nestedListField2: String = "" )确保配置属性类被扫描
在Spring Boot主类上添加@ConfigurationPropertiesScan注解,保证配置属性类能被Spring正确识别并绑定。构造函数绑定的替代方案(若必须保留)
如果业务要求必须使用@ConstructorBinding,则需手动实现集合元素的合并逻辑,或者将集合元素的所有属性集中在单个配置源中定义,避免跨配置源拆分属性配置。
内容的提问来源于stack exchange,提问作者typik89
相关产品推荐
相关产品推荐

