Home > Net >  Spring boot annotation @ConfigurationProperties doesn't work correctly classes with nested coll
Spring boot annotation @ConfigurationProperties doesn't work correctly classes with nested coll

Time:08-29

There is the data class with @ConfigurationProperties and @ConstructorBinding. This class contains the field which is collection. There are several property sources ( application.yml, application-dev1.yml) which initialize the first element of the collection. Binding for this element doesn't work correctly. Values for initialazation pulls only from one property source. Expected beahavior is the same as for field of type of some nested class: merging values from all property sources.

Kotlin properties class

@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

Test

@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())
         //org.opentest4j.AssertionFailedError:
         //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"))
    }
}

The test with RootProperties failed on assertEquals("default", rootPropperties.nestedList[0].nestedListField1) because rootPropperties.nestedList[0].nestedListField1 has empty value. All other assertions tests pass sucessfully. The binding doesn't work correctly just for collection.

At the same time the test with Environment passed successfully. And Environment.getProperty("tpp.test.root.nested-list[0].nested-list-field1") resolves corrected value: "default".

Spring boot version: 2.6.4

CodePudding user response:

covered in this section of the reference documention

Possible workaround could be to switch List to a Map.

Properties class

@ConfigurationProperties("tpp.test.root-map")
@ConstructorBinding
data class RootMapPropperties(
    var rootField1: String = "",
    var rootField2: String = "",
    var nested: NestedProperties = NestedProperties(),
    var nestedMap: Map<String, NestedMapProperties> = mapOf()
) {

    data class NestedProperties(
        var nestedField1: String = "",
        var nestedField2: String = ""
    )

    data class NestedMapProperties(
        var nestedMapField1: String = "",
        var nestedMapField2: String = ""
    )
}

application.yml

tpp:
  test:    
    root-map:
      root-field1: default
      nested:
        nested-field1: default
      nested-map:
        1:
          nested-map-field1: default

application-dev1.yml

tpp:    
    root-map:
      root-field2: dev1
      nested:
        nested-field2: dev1
      nested-map:
        1:
          nested-map-field2: dev1

Test

@ActiveProfiles("dev1")
@SpringBootTest
internal class ConfigurationPropertiesMapTest {

    @Autowired
    lateinit var environment: Environment

    @Autowired
    lateinit var rootPropperties: RootMapPropperties

    @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.nestedMap.isNotEmpty())
        Assertions.assertEquals("default", rootPropperties.nestedMap["1"]!!.nestedMapField1)
        Assertions.assertEquals("dev1", rootPropperties.nestedMap["1"]!!.nestedMapField2)
    }
}
  • Related