From 0487c589e2a4add3541b9b96bfe6f2a74f7411b0 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Sat, 12 Oct 2024 08:59:53 +0800 Subject: [PATCH] Support to inherit common properties for configuring multiple beans It's used for configuring multiple beans (#15732), we could extract common or use primary properties as parent now. Take `org.springframework.boot.autoconfigure.data.redis.RedisProperties` for example, given: ``` # primary spring.data.redis: host: 127.0.0.1 port: 6379 # additional additional.data.redis: port: 6380 ``` Then effective properties: ``` additional.data.redis: host: 127.0.0.1 port: 6380 ``` should be bound to `additionalRedisProperties`: ```java @Bean(autowireCandidate = false) // do not back off autoconfigured one @ConfigurationProperties(prefix = "additional.data.redis", inheritedPrefix = "spring.data.redis") RedisProperties additionalRedisProperties() { return new RedisProperties(); } ``` --- .../properties/ConfigurationProperties.java | 9 ++ .../ConfigurationPropertiesTests.java | 84 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java index 7c8c41e05ad3..8fadb6690971 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java @@ -39,6 +39,7 @@ * values are externalized. * * @author Dave Syer + * @author Yanming Zhou * @since 1.0.0 * @see ConfigurationPropertiesScan * @see ConstructorBinding @@ -69,6 +70,14 @@ @AliasFor("value") String prefix() default ""; + /** + * The prefix of the properties that {@link #prefix()} will inherit, It's used for + * configuring multiple beans which share common properties. + * @return the prefix of the properties to inherit + * @see #prefix() + */ + String inheritedPrefix() default ""; + /** * Flag to indicate that when binding to this object invalid fields should be ignored. * Invalid means invalid according to the binder that is used, and usually this means diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 392a7804bf3f..b72da9a939de 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -128,6 +128,7 @@ * @author Stephane Nicoll * @author Madhura Bhave * @author Vladislav Kisel + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) class ConfigurationPropertiesTests { @@ -1270,6 +1271,25 @@ void loadWhenBindingToJavaBeanWithConversionToCustomListImplementation() { assertThat(this.context.getBean(SetterBoundCustomListProperties.class).getValues()).containsExactly("a", "b"); } + @Test + void loadWhenUsingInheritedPrefixForJavaBeanBinder() { + load(SetterBoundInheritedPrefixConfiguration.class, "spring.service.host=127.0.0.1", "spring.service.port=6379", + "additional.service.port=6380"); + SetterBoundServiceProperties properties = this.context.getBean("additionalServiceProperties", + SetterBoundServiceProperties.class); + assertThat(properties.getPort()).isEqualTo(6380); + assertThat(properties.getHost()).isEqualTo("127.0.0.1"); + } + + @Test + void loadWhenUsingInheritedPrefixForValueObjectBinder() { + load(ConstructorBoundInheritedPrefixConfiguration.class, "spring.service.host=127.0.0.1", + "spring.service.port=6379", "additional.service.port=6380"); + ConstructorBoundServiceProperties properties = this.context.getBean(ConstructorBoundServiceProperties.class); + assertThat(properties.getPort()).isEqualTo(6380); + assertThat(properties.getHost()).isEqualTo("127.0.0.1"); + } + private AnnotationConfigApplicationContext load(Class configuration, String... inlinedProperties) { return load(new Class[] { configuration }, inlinedProperties); } @@ -3310,4 +3330,68 @@ static final class CustomList extends ArrayList { } + @ConfigurationProperties(prefix = "spring.service") + static class SetterBoundServiceProperties { + + private String host = "localhost"; + + private int port = 6379; + + String getHost() { + return this.host; + } + + void setHost(String host) { + this.host = host; + } + + int getPort() { + return this.port; + } + + void setPort(int port) { + this.port = port; + } + + } + + @EnableConfigurationProperties(SetterBoundServiceProperties.class) + static class SetterBoundInheritedPrefixConfiguration { + + @Bean(autowireCandidate = false) // do not back off auto-configured one + @ConfigurationProperties(prefix = "additional.service", inheritedPrefix = "spring.service") + SetterBoundServiceProperties additionalServiceProperties() { + return new SetterBoundServiceProperties(); + } + + } + + @ConfigurationProperties(prefix = "additional.service", inheritedPrefix = "spring.service") + static class ConstructorBoundServiceProperties { + + private final String host; + + private final int port; + + public ConstructorBoundServiceProperties(@DefaultValue("localhost") String host, + @DefaultValue("6379") int port) { + this.host = host; + this.port = port; + } + + String getHost() { + return this.host; + } + + int getPort() { + return this.port; + } + + } + + @EnableConfigurationProperties(ConstructorBoundServiceProperties.class) + static class ConstructorBoundInheritedPrefixConfiguration { + + } + }