I want duplicate configuration properties base on existing one, It works fine with 2.x but failed since 3.0.0, here is code snippet:
public class TestProperties {
private String foo;
private String bar;
// getter setter
}
@ConfigurationProperties(prefix = "test")
public class MainTestProperties extends TestProperties {
}
@ConfigurationProperties(prefix = "another.test")
public class AnotherTestProperties extends TestProperties {
public AnotherTestProperties(MainTestProperties properties) {
BeanUtils.copyProperties(properties, this); //properties is null since v3.0.0
}
}
Here is the full runnable unit test:
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.TestPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@TestPropertySource(properties = {"test.foo=foo", "test.bar=bar", "another.test.bar=anotherBar"})
class ConfigurationPropertiesTests {
@Autowired
private MainConfiguration mainConfiguration;
@Autowired
private AnotherConfiguration anotherConfiguration;
@Test
void test() {
TestProperties mainProperties = mainConfiguration.getProperties();
TestProperties anotherProperties = anotherConfiguration.getProperties();
assertThat(mainProperties.getFoo()).isEqualTo("foo");
assertThat(mainProperties.getBar()).isEqualTo("bar");
assertThat(anotherProperties.getFoo()).isEqualTo("foo"); // copied from mainProperties
assertThat(anotherProperties.getBar()).isEqualTo("anotherBar");
}
public static class TestProperties {
private String foo;
private String bar;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(MainConfiguration.MainTestProperties.class)
public static class MainConfiguration {
private final TestProperties properties;
public TestProperties getProperties() {
return properties;
}
public MainConfiguration(MainTestProperties properties) {
this.properties = properties;
}
@ConfigurationProperties(prefix = "test")
public static class MainTestProperties extends TestProperties {
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(AnotherConfiguration.AnotherTestProperties.class)
public static class AnotherConfiguration {
private final AnotherTestProperties properties;
public TestProperties getProperties() {
return properties;
}
public AnotherConfiguration(AnotherTestProperties properties) {
this.properties = properties;
}
@ConfigurationProperties(prefix = "another.test")
public static class AnotherTestProperties extends TestProperties {
public AnotherTestProperties(MainConfiguration.MainTestProperties properties) {
BeanUtils.copyProperties(properties, this);
}
}
}
}
Here is a minimal project demo.zip
Comment From: quaff
This regression is fixed by adding explicit @Autowired on constructer.
Comment From: quaff
Mark the constructor as private works fine according to: https://github.com/spring-projects/spring-boot/blob/837ea04cd15b44e8f6e6485f78eac8f53ca8dfd7/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java#L180-L186
It's hard to distinguish implicit @Autowired constructor from implicit @ConstructorBinding constructor, and the use case is rare, I accept such breaking change totally. Spring Boot should update the javadoc of ConfigurationProperties and the migration guide.
Comment From: snicoll
I agree that the documentation is a bit light and could use something a bit more explicit, especially as flagging it private is also a way to opt-out.