Currently ConfigurationPropertiesBindConstructorProvider requires that @ConstructorBinding be used to indicate that a @ConfigurationProperties instance should use the ValueObjectBinder. I think we could skip the need for this if there is a single constructor with one or more arguments.
Somewhat related to #23172
Comment From: Davio
Would this mean that my @ConfigurationProperties classes which are currently annotated by Lombok's @Data (which creates getters and setters for my non-final fields) can be annotated by @Value instead (which creates final fields with an all args constructor)?
If so, I think that would be a reasonable improvement.
Comment From: snicoll
I think we could skip the need for this if there is a single constructor with one or more arguments.
I don't think we can/should. I remember us having a very lengthy conversation that concluded with the current arrangement. One problem with this approach is that you can't opt-out if you happen to have a single constructor and you don't want it to be used that way (can be surprising for folks having an existing class that uses javabean conventions and a constructor for whatever reason).
Comment From: Davio
Maybe it's an idea to consolidate @ConfigurationProperties and @ConstructorBinding by adding the binding mode as an optional property to @ConfigurationProperties or something like useConstructor = true which would work if there is a single constructor?
Would it work if the constructor only sets some of the properties, but others should still be set by setters?
Comment From: philwebb
@snicoll I remember some discussions, but I couldn't remember exactly why we landed on the current setup.
One problem with this approach is that you can't opt-out if you happen to have a single constructor and you don't want it to be used that way
Ah yes, I remember now. The issue is that you may have a @ConfigurationProperties class that is either a @Component or is loaded via a @Import. In those situations you want Spring to inject beans into the constructor and getter/setter binding to be applied afterwards.
Comment From: philwebb
Reopening to check why Spring Cloud need to make this change. The exception they have is:
08:34:17 Caused by: java.lang.IllegalStateException: Cannot bind @ConfigurationProperties for bean 'configServerHealthIndicator'. Ensure that @ConstructorBinding has not been applied to regular bean
08:34:17 at org.springframework.util.Assert.state(Assert.java:76)
08:34:17 at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:86)
08:34:17 at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:78)
08:34:17 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:425)
08:34:17 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1733)
08:34:17 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:416)
08:34:17 at org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder.rebind(ConfigurationPropertiesRebinder.java:105)
08:34:17 at org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration.afterSingletonsInstantiated(ConfigurationPropertiesRebinderAutoConfiguration.java:73)
08:34:17 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:945)
08:34:17 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917)
08:34:17 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
08:34:17 at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144)
08:34:17 at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:720)
08:34:17 at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:401)
08:34:17 at org.springframework.boot.SpringApplication.run(SpringApplication.java:302)
08:34:17 at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:124)
08:34:17 at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
08:34:17 at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:123)
08:34:17 ... 39 common frames omitted
It seems like we should have detected that it was from a @Bean method.