Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document ways to opt out from immutable @ConfigurationProperties binding with single constructor #34820

Closed
quaff opened this issue Mar 30, 2023 · 3 comments
Labels
type: documentation A documentation update
Milestone

Comments

@quaff
Copy link
Contributor

quaff commented Mar 30, 2023

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

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 30, 2023
@quaff
Copy link
Contributor Author

quaff commented Mar 30, 2023

This regression is fixed by adding explicit @Autowired on constructer.

@quaff
Copy link
Contributor Author

quaff commented Apr 3, 2023

Mark the constructor as private works fine according to:

private static Constructor<?> deduceBindConstructor(Class<?> type, Constructor<?>[] candidates) {
if (candidates.length == 1 && candidates[0].getParameterCount() > 0) {
if (type.isMemberClass() && Modifier.isPrivate(candidates[0].getModifiers())) {
return null;
}
return candidates[0];
}

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.

@snicoll
Copy link
Member

snicoll commented Apr 4, 2023

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.

@snicoll snicoll added type: documentation A documentation update and removed status: waiting-for-triage An issue we've not yet triaged labels Apr 4, 2023
@snicoll snicoll added this to the 3.0.x milestone Apr 4, 2023
@snicoll snicoll changed the title @ConfigurationProperties bean can not be constructed base on existing one since v3.0.0 Document ways to opt-out from immutable @ConfigurationProperties binding with single constructor Apr 4, 2023
@philwebb philwebb modified the milestones: 3.0.x, 3.1.x Nov 8, 2023
@philwebb philwebb modified the milestones: 3.1.x, 3.1.12 May 21, 2024
@wilkinsona wilkinsona changed the title Document ways to opt-out from immutable @ConfigurationProperties binding with single constructor Document ways to opt out from immutable @ConfigurationProperties binding with single constructor May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: documentation A documentation update
Projects
None yet
Development

No branches or pull requests

4 participants