spring-cloud-commons icon indicating copy to clipboard operation
spring-cloud-commons copied to clipboard

ConfigurationPropertiesRebinder not compatible with @ConstructorBinding when constructed in bootstrap

Open symposion opened this issue 4 years ago • 7 comments

Using Hoxton.SR8

If you define an @ConfigurationProperties class that uses @ConstructorBinding, attempts to rebind it using ConfigurationPropertiesRebinder will fail with an exception from ConfigurationPropertiesBindingPostProcessor: ""Cannot bind @ConfigurationProperties for bean XXXX Ensure that @ConstructorBinding has not been applied to regular bean"

This is especially problematic because Spring Cloud Context rebinds all ConfigurationProperties classes that have been used in the bootstrap context in order to ensure that property changes during the bootstrap phase are picked up. Looking at the code, it's not clear to me that there's a simple fix for this: the rebinder fundamentally assumes that the properties bean is mutable. In order to support immutable properties classes I guess it would need some sort of proxy in the middle to allow the properties bean to be re-constructed without having to reinject it into every dependent bean.

symposion avatar Nov 03 '20 15:11 symposion

We are not going to support rebinding @ConstructorBinding properties.

spencergibb avatar Nov 03 '20 17:11 spencergibb

I'm unable to reproduce the error log. When I do it, it value doesn't update and no exception is logged. Can you provide a complete, minimal, verifiable sample that reproduces the problem? It should be available as a GitHub (or similar) project or attached to this issue as a zip file.

spencergibb avatar Nov 03 '20 17:11 spencergibb

https://github.com/symposion/spring-test (The master branch)

I've created an immutable @ConfigurationProperties class that I have no intention of mutating, I'm not explicitly refreshsing /rebinding anything, and the app fails to startup because internally Spring cloud is rebinding all the properties from the bootstrap context. I understand why that's done, but at the very least this is surprising and worth calling out in the docs as a potential "gotcha". A nicer alternative might be to just ignore properties beans that are constructor-bound when rebinding with a warning in the logs rather than a hard fail; or make the post-bootstrap rebinding optional somehow?

symposion avatar Nov 05 '20 11:11 symposion

This only applies to constructor binding beans that are created in a bootstrap configuration. Considering this is relatively uncommon and I"m not sure how to distinguish if it was created in bootstrap or not, I'm going to mark this as waiting for votes.

spencergibb avatar Mar 30 '21 22:03 spencergibb

FWIW, I just ran into this problem

candrews avatar Apr 20 '21 19:04 candrews

Pretty sure I encountered this as well. I'm trying to create something like spring-cloud-aws-parameter-store-config that uses the v2 AWS SDK and has different rules for the parameter store path. My class is simply:

@ConstructorBinding
@ConfigurationProperties(AwsParamStoreProperties.CONFIG_PREFIX)
data class AwsParamStoreProperties(
    val region: String? = null,
    val endpoint: URI? = null,
    val name: String? = null,
    val enabled: Boolean = true
) {
    companion object {
        const val CONFIG_PREFIX = "aws.paramstore"
    }
}

It loads the parameters from SSM, but when the application context tries to import the ConfigData from the bootstrap context, it throws the same exception. It does work when defined like this:

@ConfigurationProperties(AwsParamStoreProperties.CONFIG_PREFIX)
class AwsParamStoreProperties {
    var region: String? = null
    var endpoint: URI? = null
    var name: String? = null
    var enabled: Boolean = true

    companion object {
        const val CONFIG_PREFIX = "aws.paramstore"
    }
}

But then we lose immutability.

Using Spring Boot 2.5.1, spring-cloud-context 3.0.3.

efenderbosch avatar Jun 28 '21 14:06 efenderbosch

Are there any other workarounds?

sjoerd222888 avatar Feb 03 '22 16:02 sjoerd222888