From 695de2c6f50fea45f9f97d414ca59615e0c4811c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 13 Dec 2019 11:31:24 -0800 Subject: [PATCH] Polish end-to-end configuration properties tracing See gh-14880 --- ...ConfigurationPropertiesReportEndpoint.java | 139 +++++++++--------- ...gurationPropertiesReportEndpointTests.java | 55 +++---- ...java => BoundConfigurationProperties.java} | 45 ++++-- .../ConfigurationPropertiesBinder.java | 18 +-- ...nableConfigurationPropertiesRegistrar.java | 2 +- .../bind/BoundPropertiesHolder.java | 35 ----- .../BoundPropertiesTrackingBindHandler.java | 14 +- .../ConfigurationPropertiesTests.java | 8 +- ...undPropertiesTrackingBindHandlerTests.java | 15 +- 9 files changed, 153 insertions(+), 178 deletions(-) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/{ConfigurationPropertiesBoundPropertiesHolder.java => BoundConfigurationProperties.java} (60%) delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesHolder.java diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index fd5bca8b66..61068e3059 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -54,9 +54,9 @@ import org.springframework.beans.BeansException; import org.springframework.boot.actuate.endpoint.Sanitizer; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.context.properties.BoundConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationPropertiesBean; -import org.springframework.boot.context.properties.ConfigurationPropertiesBoundPropertiesHolder; import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; @@ -111,30 +111,73 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext } private ApplicationConfigurationProperties extract(ApplicationContext context) { - Map contextProperties = new HashMap<>(); + ObjectMapper mapper = getObjectMapper(); + Map contexts = new HashMap<>(); ApplicationContext target = context; while (target != null) { - contextProperties.put(target.getId(), describeConfigurationProperties(target, getObjectMapper())); + contexts.put(target.getId(), describeBeans(mapper, target)); target = target.getParent(); } - return new ApplicationConfigurationProperties(contextProperties); + return new ApplicationConfigurationProperties(contexts); } - private ContextConfigurationProperties describeConfigurationProperties(ApplicationContext context, - ObjectMapper mapper) { + private ObjectMapper getObjectMapper() { + if (this.objectMapper == null) { + this.objectMapper = new ObjectMapper(); + configureObjectMapper(this.objectMapper); + } + return this.objectMapper; + } + + /** + * Configure Jackson's {@link ObjectMapper} to be used to serialize the + * {@link ConfigurationProperties @ConfigurationProperties} objects into a {@link Map} + * structure. + * @param mapper the object mapper + */ + protected void configureObjectMapper(ObjectMapper mapper) { + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + mapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); + mapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true); + mapper.setSerializationInclusion(Include.NON_NULL); + applyConfigurationPropertiesFilter(mapper); + applySerializationModifier(mapper); + mapper.registerModule(new JavaTimeModule()); + } + + private void applyConfigurationPropertiesFilter(ObjectMapper mapper) { + mapper.setAnnotationIntrospector(new ConfigurationPropertiesAnnotationIntrospector()); + mapper.setFilterProvider( + new SimpleFilterProvider().setDefaultFilter(new ConfigurationPropertiesPropertyFilter())); + } + + /** + * Ensure only bindable and non-cyclic bean properties are reported. + * @param mapper the object mapper + */ + private void applySerializationModifier(ObjectMapper mapper) { + SerializerFactory factory = BeanSerializerFactory.instance + .withSerializerModifier(new GenericSerializerModifier()); + mapper.setSerializerFactory(factory); + } + + private ContextConfigurationProperties describeBeans(ObjectMapper mapper, ApplicationContext context) { Map beans = ConfigurationPropertiesBean.getAll(context); Map descriptors = new HashMap<>(); - beans.forEach((beanName, bean) -> { - String prefix = bean.getAnnotation().prefix(); - descriptors.put(beanName, - new ConfigurationPropertiesBeanDescriptor(prefix, - sanitize(prefix, safeSerialize(mapper, bean.getInstance(), prefix)), - getInputs(prefix, safeSerialize(mapper, bean.getInstance(), prefix)))); - }); + beans.forEach((beanName, bean) -> descriptors.put(beanName, describeBean(mapper, bean))); return new ContextConfigurationProperties(descriptors, (context.getParent() != null) ? context.getParent().getId() : null); } + private ConfigurationPropertiesBeanDescriptor describeBean(ObjectMapper mapper, ConfigurationPropertiesBean bean) { + String prefix = bean.getAnnotation().prefix(); + Map serialized = safeSerialize(mapper, bean.getInstance(), prefix); + Map properties = sanitize(prefix, serialized); + Map inputs = getInputs(prefix, serialized); + return new ConfigurationPropertiesBeanDescriptor(prefix, properties, inputs); + } + /** * Cautiously serialize the bean to a map (returning a map with an error message * instead of throwing an exception if there is a problem). @@ -153,47 +196,6 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext } } - /** - * Configure Jackson's {@link ObjectMapper} to be used to serialize the - * {@link ConfigurationProperties @ConfigurationProperties} objects into a {@link Map} - * structure. - * @param mapper the object mapper - */ - protected void configureObjectMapper(ObjectMapper mapper) { - mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - mapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); - mapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true); - mapper.setSerializationInclusion(Include.NON_NULL); - applyConfigurationPropertiesFilter(mapper); - applySerializationModifier(mapper); - mapper.registerModule(new JavaTimeModule()); - } - - private ObjectMapper getObjectMapper() { - if (this.objectMapper == null) { - this.objectMapper = new ObjectMapper(); - configureObjectMapper(this.objectMapper); - } - return this.objectMapper; - } - - /** - * Ensure only bindable and non-cyclic bean properties are reported. - * @param mapper the object mapper - */ - private void applySerializationModifier(ObjectMapper mapper) { - SerializerFactory factory = BeanSerializerFactory.instance - .withSerializerModifier(new GenericSerializerModifier()); - mapper.setSerializerFactory(factory); - } - - private void applyConfigurationPropertiesFilter(ObjectMapper mapper) { - mapper.setAnnotationIntrospector(new ConfigurationPropertiesAnnotationIntrospector()); - mapper.setFilterProvider( - new SimpleFilterProvider().setDefaultFilter(new ConfigurationPropertiesPropertyFilter())); - } - /** * Sanitize all unwanted configuration properties to avoid leaking of sensitive * information. @@ -204,7 +206,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext @SuppressWarnings("unchecked") private Map sanitize(String prefix, Map map) { map.forEach((key, value) -> { - String qualifiedKey = (prefix.isEmpty() ? prefix : prefix + ".") + key; + String qualifiedKey = getQualifiedKey(prefix, key); if (value instanceof Map) { map.put(key, sanitize(qualifiedKey, (Map) value)); } @@ -239,19 +241,20 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext @SuppressWarnings("unchecked") private Map getInputs(String prefix, Map map) { + Map augmented = new LinkedHashMap<>(map); map.forEach((key, value) -> { - String qualifiedKey = (prefix.isEmpty() ? prefix : prefix + ".") + key; + String qualifiedKey = getQualifiedKey(prefix, key); if (value instanceof Map) { - map.put(key, getInputs(qualifiedKey, (Map) value)); + augmented.put(key, getInputs(qualifiedKey, (Map) value)); } else if (value instanceof List) { - map.put(key, getInputs(qualifiedKey, (List) value)); + augmented.put(key, getInputs(qualifiedKey, (List) value)); } else { - map.put(key, applyInput(qualifiedKey)); + augmented.put(key, applyInput(qualifiedKey)); } }); - return map; + return augmented; } @SuppressWarnings("unchecked") @@ -274,17 +277,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext } private Map applyInput(String qualifiedKey) { - if (!this.context.containsBean(ConfigurationPropertiesBoundPropertiesHolder.BEAN_NAME)) { + BoundConfigurationProperties bound = BoundConfigurationProperties.get(this.context); + if (bound == null) { return Collections.emptyMap(); } - ConfigurationPropertiesBoundPropertiesHolder bean = this.context.getBean( - ConfigurationPropertiesBoundPropertiesHolder.BEAN_NAME, - ConfigurationPropertiesBoundPropertiesHolder.class); - Map boundProperties = bean.getProperties(); ConfigurationPropertyName currentName = ConfigurationPropertyName.adapt(qualifiedKey, '.'); - ConfigurationProperty candidate = boundProperties.get(currentName); + ConfigurationProperty candidate = bound.get(currentName); if (candidate == null && currentName.isLastElementIndexed()) { - candidate = boundProperties.get(currentName.chop(currentName.getNumberOfElements() - 1)); + candidate = bound.get(currentName.chop(currentName.getNumberOfElements() - 1)); } return (candidate != null) ? getInput(currentName.toString(), candidate) : Collections.emptyMap(); } @@ -298,11 +298,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext return input; } + private String getQualifiedKey(String prefix, String key) { + return (prefix.isEmpty() ? prefix : prefix + ".") + key; + } + /** * Extension to {@link JacksonAnnotationIntrospector} to suppress CGLIB generated bean * properties. */ - @SuppressWarnings("serial") private static class ConfigurationPropertiesAnnotationIntrospector extends JacksonAnnotationIntrospector { @Override diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java index ae48d1f7b3..713150bcae 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java @@ -52,6 +52,7 @@ import static org.assertj.core.api.Assertions.entry; * @author Stephane Nicoll * @author HaiTao Zhang */ +@SuppressWarnings("unchecked") class ConfigurationPropertiesReportEndpointTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() @@ -60,26 +61,20 @@ class ConfigurationPropertiesReportEndpointTests { @Test void descriptorWithJavaBeanBindMethodDetectsRelevantProperties() { this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class).run(assertProperties("test", - (properties) -> assertThat(properties).containsOnlyKeys("dbPassword", "myTestProperty", "duration"), - (inputs) -> { - })); + (properties) -> assertThat(properties).containsOnlyKeys("dbPassword", "myTestProperty", "duration"))); } @Test void descriptorWithValueObjectBindMethodDetectsRelevantProperties() { this.contextRunner.withUserConfiguration(ImmutablePropertiesConfiguration.class).run(assertProperties( "immutable", - (properties) -> assertThat(properties).containsOnlyKeys("dbPassword", "myTestProperty", "duration"), - (inputs) -> { - })); + (properties) -> assertThat(properties).containsOnlyKeys("dbPassword", "myTestProperty", "duration"))); } @Test void descriptorWithValueObjectBindMethodUseDedicatedConstructor() { - this.contextRunner.withUserConfiguration(MultiConstructorPropertiesConfiguration.class) - .run(assertProperties("multiconstructor", - (properties) -> assertThat(properties).containsOnly(entry("name", "test")), (inputs) -> { - })); + this.contextRunner.withUserConfiguration(MultiConstructorPropertiesConfiguration.class).run(assertProperties( + "multiconstructor", (properties) -> assertThat(properties).containsOnly(entry("name", "test")))); } @Test @@ -125,54 +120,44 @@ class ConfigurationPropertiesReportEndpointTests { @Test void descriptorDoesNotIncludePropertyWithNullValue() { - this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class).run(assertProperties("test", - (properties) -> assertThat(properties).doesNotContainKey("nullValue"), (inputs) -> { - })); + this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class) + .run(assertProperties("test", (properties) -> assertThat(properties).doesNotContainKey("nullValue"))); } @Test void descriptorWithDurationProperty() { this.contextRunner.withUserConfiguration(TestPropertiesConfiguration.class).run(assertProperties("test", - (properties) -> assertThat(properties.get("duration")).isEqualTo(Duration.ofSeconds(10).toString()), - (inputs) -> { - })); + (properties) -> assertThat(properties.get("duration")).isEqualTo(Duration.ofSeconds(10).toString()))); } @Test void descriptorWithNonCamelCaseProperty() { - this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class) - .run(assertProperties("mixedcase", - (properties) -> assertThat(properties.get("myURL")).isEqualTo("https://example.com"), - (inputs) -> { - })); + this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class).run(assertProperties( + "mixedcase", (properties) -> assertThat(properties.get("myURL")).isEqualTo("https://example.com"))); } @Test void descriptorWithMixedCaseProperty() { this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class).run(assertProperties( - "mixedcase", (properties) -> assertThat(properties.get("mIxedCase")).isEqualTo("mixed"), (inputs) -> { - })); + "mixedcase", (properties) -> assertThat(properties.get("mIxedCase")).isEqualTo("mixed"))); } @Test void descriptorWithSingleLetterProperty() { - this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class).run(assertProperties( - "mixedcase", (properties) -> assertThat(properties.get("z")).isEqualTo("zzz"), (inputs) -> { - })); + this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class) + .run(assertProperties("mixedcase", (properties) -> assertThat(properties.get("z")).isEqualTo("zzz"))); } @Test void descriptorWithSimpleBooleanProperty() { this.contextRunner.withUserConfiguration(BooleanPropertiesConfiguration.class).run(assertProperties("boolean", - (properties) -> assertThat(properties.get("simpleBoolean")).isEqualTo(true), (inputs) -> { - })); + (properties) -> assertThat(properties.get("simpleBoolean")).isEqualTo(true))); } @Test void descriptorWithMixedBooleanProperty() { this.contextRunner.withUserConfiguration(BooleanPropertiesConfiguration.class).run(assertProperties("boolean", - (properties) -> assertThat(properties.get("mixedBoolean")).isEqualTo(true), (inputs) -> { - })); + (properties) -> assertThat(properties.get("mixedBoolean")).isEqualTo(true))); } @Test @@ -181,7 +166,6 @@ class ConfigurationPropertiesReportEndpointTests { .run(assertProperties("test", (properties) -> { assertThat(properties.get("dbPassword")).isEqualTo("******"); assertThat(properties.get("myTestProperty")).isEqualTo("654321"); - }, (inputs) -> { })); } @@ -191,7 +175,6 @@ class ConfigurationPropertiesReportEndpointTests { .withPropertyValues("test.keys-to-sanitize=property").run(assertProperties("test", (properties) -> { assertThat(properties.get("dbPassword")).isEqualTo("123456"); assertThat(properties.get("myTestProperty")).isEqualTo("******"); - }, (inputs) -> { })); } @@ -201,7 +184,6 @@ class ConfigurationPropertiesReportEndpointTests { .withPropertyValues("test.keys-to-sanitize=.*pass.*").run(assertProperties("test", (properties) -> { assertThat(properties.get("dbPassword")).isEqualTo("******"); assertThat(properties.get("myTestProperty")).isEqualTo("654321"); - }, (inputs) -> { })); } @@ -215,7 +197,6 @@ class ConfigurationPropertiesReportEndpointTests { assertThat(secrets.get("mine")).isEqualTo("******"); assertThat(secrets.get("yours")).isEqualTo("******"); assertThat(hidden.get("mine")).isEqualTo("******"); - }, (inputs) -> { })); } @@ -292,6 +273,12 @@ class ConfigurationPropertiesReportEndpointTests { })); } + private ContextConsumer assertProperties(String prefix, + Consumer> properties) { + return assertProperties(prefix, properties, (inputs) -> { + }); + } + private ContextConsumer assertProperties(String prefix, Consumer> properties, Consumer> inputs) { return (context) -> { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBoundPropertiesHolder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/BoundConfigurationProperties.java similarity index 60% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBoundPropertiesHolder.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/BoundConfigurationProperties.java index 99cf14468f..0233fee615 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBoundPropertiesHolder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/BoundConfigurationProperties.java @@ -16,48 +16,73 @@ package org.springframework.boot.context.properties; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.boot.context.properties.bind.BoundPropertiesHolder; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; /** - * {@link BoundPropertiesHolder} for + * Bean to record and provide bound * {@link ConfigurationProperties @ConfigurationProperties}. * * @author Madhura Bhave * @since 2.3.0 */ -public class ConfigurationPropertiesBoundPropertiesHolder implements BoundPropertiesHolder { +public class BoundConfigurationProperties { private Map properties = new LinkedHashMap<>(); /** * The bean name that this class is registered with. */ - public static final String BEAN_NAME = ConfigurationPropertiesBoundPropertiesHolder.class.getName(); + private static final String BEAN_NAME = BoundConfigurationProperties.class.getName(); - @Override - public void recordBinding(ConfigurationProperty configurationProperty) { - Assert.notNull(configurationProperty, "ConfigurationProperty should not be null"); + void add(ConfigurationProperty configurationProperty) { this.properties.put(configurationProperty.getName(), configurationProperty); } - public Map getProperties() { - return this.properties; + /** + * Get the configuration property bound to the given name. + * @param name the property name + * @return the bound property or {@code null} + */ + public ConfigurationProperty get(ConfigurationPropertyName name) { + return this.properties.get(name); + } + + /** + * Get all bound properties. + * @return a map of all bound properties + */ + public Map getAll() { + return Collections.unmodifiableMap(this.properties); + } + + /** + * Return the {@link BoundConfigurationProperties} from the given + * {@link ApplicationContext} if it is available. + * @param context the context to search + * @return a {@link BoundConfigurationProperties} or {@code null} + */ + public static BoundConfigurationProperties get(ApplicationContext context) { + if (!context.containsBeanDefinition(BEAN_NAME)) { + return null; + } + return context.getBean(BEAN_NAME, BoundConfigurationProperties.class); } static void register(BeanDefinitionRegistry registry) { Assert.notNull(registry, "Registry must not be null"); if (!registry.containsBeanDefinition(BEAN_NAME)) { GenericBeanDefinition definition = new GenericBeanDefinition(); - definition.setBeanClass(ConfigurationPropertiesBoundPropertiesHolder.class); + definition.setBeanClass(BoundConfigurationProperties.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN_NAME, definition); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java index b21afa2ab5..7c5d64cbfe 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -104,18 +104,9 @@ class ConfigurationPropertiesBinder { return null; } - private ConfigurationPropertiesBoundPropertiesHolder getBoundPropertiesHolder() { - if (this.applicationContext.containsBean(ConfigurationPropertiesBoundPropertiesHolder.BEAN_NAME)) { - return this.applicationContext.getBean(ConfigurationPropertiesBoundPropertiesHolder.BEAN_NAME, - ConfigurationPropertiesBoundPropertiesHolder.class); - } - return null; - } - private BindHandler getBindHandler(Bindable target, ConfigurationProperties annotation) { List validators = getValidators(target); - ConfigurationPropertiesBoundPropertiesHolder holder = getBoundPropertiesHolder(); - BindHandler handler = getHandler(holder); + BindHandler handler = getHandler(); if (annotation.ignoreInvalidFields()) { handler = new IgnoreErrorsBindHandler(handler); } @@ -132,9 +123,10 @@ class ConfigurationPropertiesBinder { return handler; } - private IgnoreTopLevelConverterNotFoundBindHandler getHandler(ConfigurationPropertiesBoundPropertiesHolder holder) { - return (holder != null) - ? new IgnoreTopLevelConverterNotFoundBindHandler(new BoundPropertiesTrackingBindHandler(holder)) + private IgnoreTopLevelConverterNotFoundBindHandler getHandler() { + BoundConfigurationProperties bound = BoundConfigurationProperties.get(this.applicationContext); + return (bound != null) + ? new IgnoreTopLevelConverterNotFoundBindHandler(new BoundPropertiesTrackingBindHandler(bound::add)) : new IgnoreTopLevelConverterNotFoundBindHandler(); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java index 8c0c4a4e0b..d5edb7f650 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java @@ -49,7 +49,7 @@ class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegi @SuppressWarnings("deprecation") static void registerInfrastructureBeans(BeanDefinitionRegistry registry) { ConfigurationPropertiesBindingPostProcessor.register(registry); - ConfigurationPropertiesBoundPropertiesHolder.register(registry); + BoundConfigurationProperties.register(registry); ConfigurationPropertiesBeanDefinitionValidator.register(registry); ConfigurationBeanFactoryMetadata.register(registry); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesHolder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesHolder.java deleted file mode 100644 index 4f412d3dc0..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesHolder.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.context.properties.bind; - -import org.springframework.boot.context.properties.source.ConfigurationProperty; - -/** - * Record bound configuration properties. - * - * @author Madhura Bhave - * @since 2.3.0 - */ -public interface BoundPropertiesHolder { - - /** - * Record the bound configuration property. - * @param configurationProperty the bound property - */ - void recordBinding(ConfigurationProperty configurationProperty); - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java index 5cc98bc7e2..f2a136be1d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java @@ -16,6 +16,9 @@ package org.springframework.boot.context.properties.bind; +import java.util.function.Consumer; + +import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.util.Assert; @@ -27,18 +30,17 @@ import org.springframework.util.Assert; */ public class BoundPropertiesTrackingBindHandler extends AbstractBindHandler { - private final BoundPropertiesHolder holder; + private final Consumer consumer; - public BoundPropertiesTrackingBindHandler(BoundPropertiesHolder holder) { - super(); - Assert.notNull(holder, "Bound properties holder should not be null."); - this.holder = holder; + public BoundPropertiesTrackingBindHandler(Consumer consumer) { + Assert.notNull(consumer, "Consumer must not be null"); + this.consumer = consumer; } @Override public Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { if (context.getConfigurationProperty() != null && name.equals(context.getConfigurationProperty().getName())) { - this.holder.recordBinding(context.getConfigurationProperty()); + this.consumer.accept(context.getConfigurationProperty()); } return super.onSuccess(name, target, context, result); } 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 f412854de9..266b06ab41 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 @@ -923,11 +923,9 @@ class ConfigurationPropertiesTests { @Test void boundPropertiesShouldBeRecorded() { load(NestedConfiguration.class, "name=foo", "nested.name=bar"); - ConfigurationPropertiesBoundPropertiesHolder recorder = this.context.getBean( - ConfigurationPropertiesBoundPropertiesHolder.BEAN_NAME, - ConfigurationPropertiesBoundPropertiesHolder.class); - assertThat(recorder.getProperties().keySet().stream().map(ConfigurationPropertyName::toString)).contains("name", - "nested.name"); + BoundConfigurationProperties bound = BoundConfigurationProperties.get(this.context); + Set keys = bound.getAll().keySet(); + assertThat(keys.stream().map(ConfigurationPropertyName::toString)).contains("name", "nested.name"); } private AnnotationConfigApplicationContext load(Class configuration, String... inlinedProperties) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandlerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandlerTests.java index bc3d8a909d..7f0f8dfaa5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandlerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandlerTests.java @@ -18,16 +18,18 @@ package org.springframework.boot.context.properties.bind; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -45,21 +47,22 @@ public class BoundPropertiesTrackingBindHandlerTests { private Binder binder; - private BoundPropertiesHolder recorder; + @Mock + private Consumer consumer; @BeforeEach void setup() { + MockitoAnnotations.initMocks(this); this.binder = new Binder(this.sources); - this.recorder = mock(BoundPropertiesHolder.class); - this.handler = new BoundPropertiesTrackingBindHandler(this.recorder); + this.handler = new BoundPropertiesTrackingBindHandler(this.consumer); } @Test void handlerShouldCallRecordBindingIfConfigurationPropertyIsNotNull() { this.sources.add(new MockConfigurationPropertySource("foo.age", 4)); this.binder.bind("foo", Bindable.of(ExampleBean.class), this.handler); - verify(this.recorder, times(1)).recordBinding(any(ConfigurationProperty.class)); - verify(this.recorder, never()).recordBinding(null); + verify(this.consumer, times(1)).accept(any(ConfigurationProperty.class)); + verify(this.consumer, never()).accept(null); } static class ExampleBean {