Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: spring-projects/spring-boot
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: quaff/spring-boot
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: patch-78
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 1 commit
  • 3 files changed
  • 1 contributor

Commits on Dec 16, 2024

  1. Support to inherit common properties for configuring multiple beans

    It's used for configuring multiple beans, 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();
    	}
    ```
    quaff committed Dec 16, 2024
    Copy the full SHA
    490b706 View commit details
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -17,7 +17,9 @@
package org.springframework.boot.context.properties;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.springframework.beans.BeansException;
@@ -49,8 +51,13 @@
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
@@ -61,6 +68,7 @@
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Yanming Zhou
*/
class ConfigurationPropertiesBinder {

@@ -91,14 +99,16 @@ BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
Bindable<?> target = propertiesBean.asBindTarget();
ConfigurationProperties annotation = propertiesBean.getAnnotation();
BindHandler bindHandler = getBindHandler(target, annotation);
return getBinder().bind(annotation.prefix(), target, bindHandler);
Binder binder = getBinder(target, annotation);
return binder.bind(annotation.prefix(), target, bindHandler);
}

Object bindOrCreate(ConfigurationPropertiesBean propertiesBean) {
Bindable<?> target = propertiesBean.asBindTarget();
ConfigurationProperties annotation = propertiesBean.getAnnotation();
BindHandler bindHandler = getBindHandler(target, annotation);
return getBinder().bindOrCreate(annotation.prefix(), target, bindHandler);
Binder binder = getBinder(target, annotation);
return binder.bindOrCreate(annotation.prefix(), target, bindHandler);
}

private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext) {
@@ -178,7 +188,36 @@ private Validator getJsr303Validator(Class<?> type) {
return new ConfigurationPropertiesJsr303Validator(this.applicationContext, type);
}

private Binder getBinder() {
private <T> Binder getBinder(Bindable<T> target, ConfigurationProperties annotation) {
return StringUtils.hasText(annotation.prefix()) && StringUtils.hasText(annotation.inheritedPrefix())
? createBinderForInheritedPrefix(annotation.prefix(), annotation.inheritedPrefix()) : getDefaultBinder();
}

private Binder createBinderForInheritedPrefix(String prefix, String inheritedPrefix) {
MutablePropertySources propertySourcesToUse = new MutablePropertySources(this.propertySources);
propertySourcesToUse.addLast(createPropertySourceForInheritedPrefix(prefix, inheritedPrefix));
return new Binder(ConfigurationPropertySources.from(propertySourcesToUse),
getPropertySourcesPlaceholdersResolver(), getConversionServices(), getPropertyEditorInitializer(), null,
null);
}

private PropertySource<?> createPropertySourceForInheritedPrefix(String prefix, String inheritedPrefix) {
Map<String, Object> map = new HashMap<>();
for (PropertySource<?> propertySource : this.propertySources) {
if (propertySource instanceof EnumerablePropertySource<?> enumerablePropertySource) {
for (String key : enumerablePropertySource.getPropertyNames()) {
if (key.startsWith(inheritedPrefix + '.')) {
map.put(prefix + '.' + key.substring(inheritedPrefix.length() + 1),
enumerablePropertySource.getProperty(key));
}
}
}
}
return new MapPropertySource(
"MapPropertySource for prefix '%s' inheriting from '%s'".formatted(prefix, inheritedPrefix), map);
}

private Binder getDefaultBinder() {
if (this.binder == null) {
this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
getConversionServices(), getPropertyEditorInitializer(), null, null);
Original file line number Diff line number Diff line change
@@ -129,6 +129,7 @@
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Vladislav Kisel
* @author Yanming Zhou
*/
@ExtendWith(OutputCaptureExtension.class)
class ConfigurationPropertiesTests {
@@ -1271,6 +1272,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);
}
@@ -3311,4 +3331,68 @@ static final class CustomList<E> extends ArrayList<E> {

}

@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(defaultCandidate = 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 {

}

}