From 1db2f5f960c0bde4da834c321896369b462eac7f Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 7 Oct 2020 11:34:57 -0700 Subject: [PATCH] Support Formatter conversion service beans Update `ConversionServiceDeducer` to also include `Formatter` beans when they are qualified with `@ConfigurationPropertiesBinding`. Fixes gh-23576 --- .../properties/ConversionServiceDeducer.java | 10 +++- .../ConfigurationPropertiesTests.java | 53 +++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java index 43eb4651e4..3c053e0588 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java @@ -30,6 +30,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.format.Formatter; /** * Utility to deduce the {@link ConversionService} to use for configuration properties @@ -62,9 +63,13 @@ class ConversionServiceDeducer { private final List genericConverters; + @SuppressWarnings("rawtypes") + private final List formatters; + Factory(BeanFactory beanFactory) { this.converters = beans(beanFactory, Converter.class, ConfigurationPropertiesBinding.VALUE); this.genericConverters = beans(beanFactory, GenericConverter.class, ConfigurationPropertiesBinding.VALUE); + this.formatters = beans(beanFactory, Formatter.class, ConfigurationPropertiesBinding.VALUE); } private List beans(BeanFactory beanFactory, Class type, String qualifier) { @@ -80,7 +85,7 @@ class ConversionServiceDeducer { } public ConversionService create() { - if (this.converters.isEmpty() && this.genericConverters.isEmpty()) { + if (this.converters.isEmpty() && this.genericConverters.isEmpty() && this.formatters.isEmpty()) { return ApplicationConversionService.getSharedInstance(); } ApplicationConversionService conversionService = new ApplicationConversionService(); @@ -90,6 +95,9 @@ class ConversionServiceDeducer { for (GenericConverter genericConverter : this.genericConverters) { conversionService.addConverter(genericConverter); } + for (Formatter formatter : this.formatters) { + conversionService.addFormatter(formatter); + } return conversionService; } 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 292d14921c..91238fab20 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 @@ -18,12 +18,14 @@ package org.springframework.boot.context.properties; import java.beans.PropertyEditorSupport; import java.io.File; +import java.text.ParseException; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -66,6 +68,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ProtocolResolver; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.format.Formatter; import org.springframework.mock.env.MockEnvironment; import org.springframework.stereotype.Component; import org.springframework.test.context.support.TestPropertySourceUtils; @@ -597,7 +600,7 @@ public class ConfigurationPropertiesTests { } @Test - public void loadShouldUseConfigurationConverter() { + public void loadShouldUseConverterBean() { prepareConverterContext(ConverterConfiguration.class, PersonProperties.class); Person person = this.context.getBean(PersonProperties.class).getPerson(); assertThat(person.firstName).isEqualTo("John"); @@ -613,13 +616,21 @@ public class ConfigurationPropertiesTests { } @Test - public void loadShouldUseGenericConfigurationConverter() { + public void loadShouldUseGenericConverterBean() { prepareConverterContext(GenericConverterConfiguration.class, PersonProperties.class); Person person = this.context.getBean(PersonProperties.class).getPerson(); assertThat(person.firstName).isEqualTo("John"); assertThat(person.lastName).isEqualTo("Smith"); } + @Test + public void loadShouldUseFormatterBean() { + prepareConverterContext(FormatterConfiguration.class, PersonProperties.class); + Person person = this.context.getBean(PersonProperties.class).getPerson(); + assertThat(person.firstName).isEqualTo("John"); + assertThat(person.lastName).isEqualTo("Smith"); + } + @Test public void loadWhenGenericConfigurationConverterIsNotQualifiedShouldNotConvert() { assertThatExceptionOfType(BeanCreationException.class).isThrownBy( @@ -1043,6 +1054,17 @@ public class ConfigurationPropertiesTests { } + @Configuration + static class FormatterConfiguration { + + @Bean + @ConfigurationPropertiesBinding + Formatter personFormatter() { + return new PersonFormatter(); + } + + } + @Configuration static class NonQualifiedGenericConverterConfiguration { @@ -1731,12 +1753,27 @@ public class ConfigurationPropertiesTests { } + static class PersonFormatter implements Formatter { + + @Override + public String print(Person person, Locale locale) { + return person.getFirstName() + " " + person.getLastName(); + } + + @Override + public Person parse(String text, Locale locale) throws ParseException { + String[] content = text.split(" "); + return new Person(content[0], content[1]); + } + + } + static class PersonPropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { - String[] split = text.split(","); - setValue(new Person(split[1], split[0])); + String[] content = text.split(","); + setValue(new Person(content[1], content[0])); } } @@ -1752,6 +1789,14 @@ public class ConfigurationPropertiesTests { this.lastName = lastName; } + String getFirstName() { + return this.firstName; + } + + String getLastName() { + return this.lastName; + } + } static class Foo {