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 e1b19ae9bd4..4adac0a168a 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 @@ -166,7 +166,7 @@ class ConfigurationPropertiesBinder { private Binder getBinder() { if (this.binder == null) { this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(), - getConversionService(), getPropertyEditorInitializer(), null, + getConversionServices(), getPropertyEditorInitializer(), null, ConfigurationPropertiesBindConstructorProvider.INSTANCE); } return this.binder; @@ -180,8 +180,8 @@ class ConfigurationPropertiesBinder { return new PropertySourcesPlaceholdersResolver(this.propertySources); } - private ConversionService getConversionService() { - return new ConversionServiceDeducer(this.applicationContext).getConversionService(); + private List getConversionServices() { + return new ConversionServiceDeducer(this.applicationContext).getConversionServices(); } private Consumer getPropertyEditorInitializer() { 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 1b0e255ab57..1a78498c534 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -20,10 +20,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -31,6 +30,7 @@ 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; +import org.springframework.format.FormatterRegistry; /** * Utility to deduce the {@link ConversionService} to use for configuration properties @@ -46,17 +46,38 @@ class ConversionServiceDeducer { this.applicationContext = applicationContext; } - ConversionService getConversionService() { - try { - return this.applicationContext.getBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, - ConversionService.class); + List getConversionServices() { + if (hasUserDefinedConfigurationServiceBean()) { + return Collections.singletonList(this.applicationContext + .getBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } - catch (NoSuchBeanDefinitionException ex) { - return new Factory(this.applicationContext.getAutowireCapableBeanFactory()).create(); + if (this.applicationContext instanceof ConfigurableApplicationContext) { + return getConversionServices((ConfigurableApplicationContext) this.applicationContext); } + return null; } - private static class Factory { + private List getConversionServices(ConfigurableApplicationContext applicationContext) { + List conversionServices = new ArrayList<>(); + if (applicationContext.getBeanFactory().getConversionService() != null) { + conversionServices.add(applicationContext.getBeanFactory().getConversionService()); + } + ConverterBeans converterBeans = new ConverterBeans(applicationContext); + if (!converterBeans.isEmpty()) { + ApplicationConversionService beansConverterService = new ApplicationConversionService(); + converterBeans.addTo(beansConverterService); + conversionServices.add(beansConverterService); + } + return conversionServices; + } + + private boolean hasUserDefinedConfigurationServiceBean() { + String beanName = ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME; + return this.applicationContext.containsBean(beanName) && this.applicationContext.getAutowireCapableBeanFactory() + .isTypeMatch(beanName, ConversionService.class); + } + + private static class ConverterBeans { @SuppressWarnings("rawtypes") private final List converters; @@ -66,17 +87,11 @@ class ConversionServiceDeducer { @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) { - if (beanFactory instanceof ListableBeanFactory) { - return beans(type, qualifier, (ListableBeanFactory) beanFactory); - } - return Collections.emptyList(); + ConverterBeans(ConfigurableApplicationContext applicationContext) { + ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); + this.converters = beans(Converter.class, ConfigurationPropertiesBinding.VALUE, beanFactory); + this.genericConverters = beans(GenericConverter.class, ConfigurationPropertiesBinding.VALUE, beanFactory); + this.formatters = beans(Formatter.class, ConfigurationPropertiesBinding.VALUE, beanFactory); } private List beans(Class type, String qualifier, ListableBeanFactory beanFactory) { @@ -84,21 +99,20 @@ class ConversionServiceDeducer { BeanFactoryAnnotationUtils.qualifiedBeansOfType(beanFactory, type, qualifier).values()); } - ConversionService create() { - if (this.converters.isEmpty() && this.genericConverters.isEmpty() && this.formatters.isEmpty()) { - return ApplicationConversionService.getSharedInstance(); - } - ApplicationConversionService conversionService = new ApplicationConversionService(); + boolean isEmpty() { + return this.converters.isEmpty() && this.genericConverters.isEmpty() && this.formatters.isEmpty(); + } + + void addTo(FormatterRegistry registry) { for (Converter converter : this.converters) { - conversionService.addConverter(converter); + registry.addConverter(converter); } for (GenericConverter genericConverter : this.genericConverters) { - conversionService.addConverter(genericConverter); + registry.addConverter(genericConverter); } for (Formatter formatter : this.formatters) { - conversionService.addFormatter(formatter); + registry.addFormatter(formatter); } - return conversionService; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java index b0edee870fb..ef9d8be2e48 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -38,7 +38,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Utility to handle any conversion needed during binding. This class is not thread-safe @@ -49,125 +49,92 @@ import org.springframework.util.Assert; */ final class BindConverter { - private static final Set> EXCLUDED_EDITORS; - static { - Set> excluded = new HashSet<>(); - excluded.add(FileEditor.class); // gh-12163 - EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded); - } - private static BindConverter sharedInstance; - private final ConversionService conversionService; + private final List delegates; - private BindConverter(ConversionService conversionService, + private BindConverter(List conversionServices, Consumer propertyEditorInitializer) { - Assert.notNull(conversionService, "ConversionService must not be null"); - List conversionServices = getConversionServices(conversionService, - propertyEditorInitializer); - this.conversionService = new CompositeConversionService(conversionServices); + List delegates = new ArrayList<>(); + delegates.add(new TypeConverterConversionService(propertyEditorInitializer)); + boolean hasApplication = false; + if (!CollectionUtils.isEmpty(conversionServices)) { + for (ConversionService conversionService : conversionServices) { + delegates.add(conversionService); + hasApplication = hasApplication || conversionService instanceof ApplicationConversionService; + } + } + if (!hasApplication) { + delegates.add(ApplicationConversionService.getSharedInstance()); + } + this.delegates = Collections.unmodifiableList(delegates); } - private List getConversionServices(ConversionService conversionService, - Consumer propertyEditorInitializer) { - List services = new ArrayList<>(); - services.add(new TypeConverterConversionService(propertyEditorInitializer)); - services.add(conversionService); - if (!(conversionService instanceof ApplicationConversionService)) { - services.add(ApplicationConversionService.getSharedInstance()); - } - return services; + boolean canConvert(Object source, ResolvableType targetType, Annotation... targetAnnotations) { + return canConvert(TypeDescriptor.forObject(source), + new ResolvableTypeDescriptor(targetType, targetAnnotations)); } - boolean canConvert(Object value, ResolvableType type, Annotation... annotations) { - return this.conversionService.canConvert(TypeDescriptor.forObject(value), - new ResolvableTypeDescriptor(type, annotations)); + private boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + for (ConversionService service : this.delegates) { + if (service.canConvert(sourceType, targetType)) { + return true; + } + } + return false; } - T convert(Object result, Bindable target) { - return convert(result, target.getType(), target.getAnnotations()); + T convert(Object source, Bindable target) { + return convert(source, target.getType(), target.getAnnotations()); } @SuppressWarnings("unchecked") - T convert(Object value, ResolvableType type, Annotation... annotations) { - if (value == null) { + T convert(Object source, ResolvableType targetType, Annotation... targetAnnotations) { + if (source == null) { return null; } - return (T) this.conversionService.convert(value, TypeDescriptor.forObject(value), - new ResolvableTypeDescriptor(type, annotations)); + return (T) convert(source, TypeDescriptor.forObject(source), + new ResolvableTypeDescriptor(targetType, targetAnnotations)); } - static BindConverter get(ConversionService conversionService, - Consumer propertyEditorInitializer) { - if (conversionService == ApplicationConversionService.getSharedInstance() - && propertyEditorInitializer == null) { - if (sharedInstance == null) { - sharedInstance = new BindConverter(conversionService, propertyEditorInitializer); + private Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + for (int i = 0; i < this.delegates.size() - 1; i++) { + try { + ConversionService delegate = this.delegates.get(i); + if (delegate.canConvert(sourceType, targetType)) { + return delegate.convert(source, sourceType, targetType); + } + } + catch (ConversionException ex) { } - return sharedInstance; } - return new BindConverter(conversionService, propertyEditorInitializer); + return this.delegates.get(this.delegates.size() - 1).convert(source, sourceType, targetType); } - /** - * A {@link TypeDescriptor} backed by a {@link ResolvableType}. - */ - private static class ResolvableTypeDescriptor extends TypeDescriptor { - - ResolvableTypeDescriptor(ResolvableType resolvableType, Annotation[] annotations) { - super(resolvableType, null, annotations); + static BindConverter get(List conversionServices, + Consumer propertyEditorInitializer) { + boolean sharedApplicationConversionService = (conversionServices == null) || (conversionServices.size() == 1 + && conversionServices.get(0) == ApplicationConversionService.getSharedInstance()); + if (propertyEditorInitializer == null && sharedApplicationConversionService) { + return getSharedInstance(); } + return new BindConverter(conversionServices, propertyEditorInitializer); + } + private static BindConverter getSharedInstance() { + if (sharedInstance == null) { + sharedInstance = new BindConverter(null, null); + } + return sharedInstance; } /** - * Composite {@link ConversionService} used to call multiple services. + * A {@link TypeDescriptor} backed by a {@link ResolvableType}. */ - static class CompositeConversionService implements ConversionService { - - private final List delegates; - - CompositeConversionService(List delegates) { - this.delegates = delegates; - } - - @Override - public boolean canConvert(Class sourceType, Class targetType) { - Assert.notNull(targetType, "Target type to convert to cannot be null"); - return canConvert((sourceType != null) ? TypeDescriptor.valueOf(sourceType) : null, - TypeDescriptor.valueOf(targetType)); - } - - @Override - public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { - for (ConversionService service : this.delegates) { - if (service.canConvert(sourceType, targetType)) { - return true; - } - } - return false; - } - - @Override - @SuppressWarnings("unchecked") - public T convert(Object source, Class targetType) { - Assert.notNull(targetType, "Target type to convert to cannot be null"); - return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); - } + private static class ResolvableTypeDescriptor extends TypeDescriptor { - @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - for (int i = 0; i < this.delegates.size() - 1; i++) { - try { - ConversionService delegate = this.delegates.get(i); - if (delegate.canConvert(sourceType, targetType)) { - return delegate.convert(source, sourceType, targetType); - } - } - catch (ConversionException ex) { - } - } - return this.delegates.get(this.delegates.size() - 1).convert(source, sourceType, targetType); + ResolvableTypeDescriptor(ResolvableType resolvableType, Annotation[] annotations) { + super(resolvableType, null, annotations); } } @@ -208,6 +175,13 @@ final class BindConverter { */ private static class TypeConverterConverter implements ConditionalGenericConverter { + private static final Set> EXCLUDED_EDITORS; + static { + Set> excluded = new HashSet<>(); + excluded.add(FileEditor.class); // gh-12163 + EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded); + } + private final SimpleTypeConverter typeConverter; TypeConverterConverter(SimpleTypeConverter typeConverter) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index bd813f47d6d..b0e67042bdf 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -60,9 +60,7 @@ public class Binder { private final PlaceholdersResolver placeholdersResolver; - private final ConversionService conversionService; - - private final Consumer propertyEditorInitializer; + private final BindConverter bindConverter; private final BindHandler defaultBindHandler; @@ -159,12 +157,34 @@ public class Binder { public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, ConversionService conversionService, Consumer propertyEditorInitializer, BindHandler defaultBindHandler, BindConstructorProvider constructorProvider) { + this(sources, placeholdersResolver, + (conversionService != null) ? Collections.singletonList(conversionService) + : (List) null, + propertyEditorInitializer, defaultBindHandler, constructorProvider); + } + + /** + * Create a new {@link Binder} instance for the specified sources. + * @param sources the sources used for binding + * @param placeholdersResolver strategy to resolve any property placeholders + * @param conversionServices the conversion services to convert values (or + * {@code null} to use {@link ApplicationConversionService}) + * @param propertyEditorInitializer initializer used to configure the property editors + * that can convert values (or {@code null} if no initialization is required). Often + * used to call {@link ConfigurableListableBeanFactory#copyRegisteredEditorsTo}. + * @param defaultBindHandler the default bind handler to use if none is specified when + * binding + * @param constructorProvider the constructor provider which provides the bind + * constructor to use when binding + * @since 2.5.0 + */ + public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, + List conversionServices, Consumer propertyEditorInitializer, + BindHandler defaultBindHandler, BindConstructorProvider constructorProvider) { Assert.notNull(sources, "Sources must not be null"); this.sources = sources; this.placeholdersResolver = (placeholdersResolver != null) ? placeholdersResolver : PlaceholdersResolver.NONE; - this.conversionService = (conversionService != null) ? conversionService - : ApplicationConversionService.getSharedInstance(); - this.propertyEditorInitializer = propertyEditorInitializer; + this.bindConverter = BindConverter.get(conversionServices, propertyEditorInitializer); this.defaultBindHandler = (defaultBindHandler != null) ? defaultBindHandler : BindHandler.DEFAULT; if (constructorProvider == null) { constructorProvider = BindConstructorProvider.DEFAULT; @@ -513,8 +533,6 @@ public class Binder { */ final class Context implements BindContext { - private final BindConverter converter; - private int depth; private final List source = Arrays.asList((ConfigurationPropertySource) null); @@ -527,10 +545,6 @@ public class Binder { private ConfigurationProperty configurationProperty; - Context() { - this.converter = BindConverter.get(Binder.this.conversionService, Binder.this.propertyEditorInitializer); - } - private void increaseDepth() { this.depth++; } @@ -602,7 +616,7 @@ public class Binder { } BindConverter getConverter() { - return this.converter; + return Binder.this.bindConverter; } @Override 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 a1d28b3ef73..bc500e3ef27 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 @@ -76,6 +76,7 @@ import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; @@ -645,6 +646,20 @@ class ConfigurationPropertiesTests { assertThat(person.lastName).isEqualTo("Smith"); } + @Test + void loadWhenBeanFactoryConversionServiceAndConverterBean() { + DefaultConversionService conversionService = new DefaultConversionService(); + conversionService.addConverter(new AlienConverter()); + this.context.getBeanFactory().setConversionService(conversionService); + load(new Class[] { ConverterConfiguration.class, PersonAndAlienProperties.class }, "test.person=John Smith", + "test.alien=Alf Tanner"); + PersonAndAlienProperties properties = this.context.getBean(PersonAndAlienProperties.class); + assertThat(properties.getPerson().firstName).isEqualTo("John"); + assertThat(properties.getPerson().lastName).isEqualTo("Smith"); + assertThat(properties.getAlien().firstName).isEqualTo("Alf"); + assertThat(properties.getAlien().lastName).isEqualTo("Tanner"); + } + @Test void loadWhenConfigurationConverterIsNotQualifiedShouldNotConvert() { assertThatExceptionOfType(BeanCreationException.class) @@ -1908,6 +1923,32 @@ class ConfigurationPropertiesTests { } + @EnableConfigurationProperties + @ConfigurationProperties(prefix = "test") + static class PersonAndAlienProperties { + + private Person person; + + private Alien alien; + + Person getPerson() { + return this.person; + } + + void setPerson(Person person) { + this.person = person; + } + + Alien getAlien() { + return this.alien; + } + + void setAlien(Alien alien) { + this.alien = alien; + } + + } + @EnableConfigurationProperties @ConfigurationProperties(prefix = "sample") static class MapWithNumericKeyProperties { @@ -2201,6 +2242,16 @@ class ConfigurationPropertiesTests { } + static class AlienConverter implements Converter { + + @Override + public Alien convert(String source) { + String[] content = StringUtils.split(source, " "); + return new Alien(content[0], content[1]); + } + + } + static class GenericPersonConverter implements GenericConverter { @Override @@ -2262,6 +2313,27 @@ class ConfigurationPropertiesTests { } + static class Alien { + + private final String firstName; + + private final String lastName; + + Alien(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + String getFirstName() { + return this.firstName; + } + + String getLastName() { + return this.lastName; + } + + } + static class Foo { private String a; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java index 59771e947e5..30286fe950b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -18,6 +18,7 @@ package org.springframework.boot.context.properties; import java.io.InputStream; import java.io.OutputStream; +import java.util.List; import org.junit.jupiter.api.Test; @@ -40,28 +41,42 @@ import static org.assertj.core.api.Assertions.assertThat; class ConversionServiceDeducerTests { @Test - void getConversionServiceWhenHasConversionServiceBeanReturnsBean() { + void getConversionServicesWhenHasConversionServiceBeanContainsOnlyBean() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext( CustomConverterServiceConfiguration.class); ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext); - assertThat(deducer.getConversionService()).isInstanceOf(TestApplicationConversionService.class); + TestApplicationConversionService expected = applicationContext.getBean(TestApplicationConversionService.class); + assertThat(deducer.getConversionServices()).containsExactly(expected); } @Test - void getConversionServiceWhenHasNoConversionServiceBeanAndNoQualifiedBeansReturnsSharedInstance() { + void getConversionServiceWhenHasNoConversionServiceBeanAndNoQualifiedBeansAndNoBeanFactoryConversionServiceReturnsEmptyList() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(EmptyConfiguration.class); ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext); - assertThat(deducer.getConversionService()).isSameAs(ApplicationConversionService.getSharedInstance()); + assertThat(deducer.getConversionServices()).isEmpty(); } @Test - void getConversionServiceWhenHasQualifiedConverterBeansReturnsNewInstance() { + void getConversionServiceWhenHasNoConversionServiceBeanAndNoQualifiedBeansAndBeanFactoryConversionServiceContainsOnlyBeanFactoryInstance() { + ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext( + EmptyConfiguration.class); + ConversionService conversionService = new ApplicationConversionService(); + applicationContext.getBeanFactory().setConversionService(conversionService); + ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext); + List conversionServices = deducer.getConversionServices(); + assertThat(conversionServices).containsOnly(conversionService); + assertThat(conversionServices.get(0)).isSameAs(conversionService); + } + + @Test + void getConversionServiceWhenHasQualifiedConverterBeansContainsCustomizedApplicationService() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext( CustomConverterConfiguration.class); ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext); - ConversionService conversionService = deducer.getConversionService(); - assertThat(conversionService).isNotSameAs(ApplicationConversionService.getSharedInstance()); - assertThat(conversionService.canConvert(InputStream.class, OutputStream.class)).isTrue(); + List conversionServices = deducer.getConversionServices(); + assertThat(conversionServices).hasSize(1); + assertThat(conversionServices.get(0)).isNotSameAs(ApplicationConversionService.getSharedInstance()); + assertThat(conversionServices.get(0).canConvert(InputStream.class, OutputStream.class)).isTrue(); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java index e50b925b461..5eadc1c6f3a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -19,6 +19,7 @@ package org.springframework.boot.context.properties.bind; import java.beans.PropertyEditorSupport; import java.io.File; import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -28,7 +29,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.PropertyEditorRegistry; -import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConverterNotFoundException; @@ -38,7 +38,6 @@ import org.springframework.core.convert.support.GenericConversionService; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; @@ -54,20 +53,14 @@ class BindConverterTests { @Mock private Consumer propertyEditorInitializer; - @Test - void createWhenConversionServiceIsNullShouldThrowException() { - assertThatIllegalArgumentException().isThrownBy(() -> BindConverter.get(null, null)) - .withMessageContaining("ConversionService must not be null"); - } - @Test void createWhenPropertyEditorInitializerIsNullShouldCreate() { - BindConverter.get(ApplicationConversionService.getSharedInstance(), null); + BindConverter.get(null, null); } @Test void createWhenPropertyEditorInitializerIsNotNullShouldUseToInitialize() { - BindConverter.get(ApplicationConversionService.getSharedInstance(), this.propertyEditorInitializer); + BindConverter.get(null, this.propertyEditorInitializer); verify(this.propertyEditorInitializer).accept(any(PropertyEditorRegistry.class)); } @@ -111,7 +104,7 @@ class BindConverterTests { @Test void canConvertWhenNotPropertyEditorAndConversionServiceCannotConvertShouldReturnFalse() { - BindConverter bindConverter = BindConverter.get(ApplicationConversionService.getSharedInstance(), null); + BindConverter bindConverter = BindConverter.get(null, null); assertThat(bindConverter.canConvert("test", ResolvableType.forClass(SampleType.class))).isFalse(); } @@ -162,7 +155,7 @@ class BindConverterTests { @Test void convertWhenNotPropertyEditorAndConversionServiceCannotConvertShouldThrowException() { - BindConverter bindConverter = BindConverter.get(ApplicationConversionService.getSharedInstance(), null); + BindConverter bindConverter = BindConverter.get(null, null); assertThatExceptionOfType(ConverterNotFoundException.class) .isThrownBy(() -> bindConverter.convert("test", ResolvableType.forClass(SampleType.class))); } @@ -171,27 +164,29 @@ class BindConverterTests { void convertWhenConvertingToFileShouldExcludeFileEditor() { // For back compatibility we want true file conversion and not an accidental // classpath resource reference. See gh-12163 - BindConverter bindConverter = BindConverter.get(new GenericConversionService(), null); + BindConverter bindConverter = BindConverter.get(Collections.singletonList(new GenericConversionService()), + null); File result = bindConverter.convert(".", ResolvableType.forClass(File.class)); assertThat(result.getPath()).isEqualTo("."); } @Test void fallsBackToApplicationConversionService() { - BindConverter bindConverter = BindConverter.get(new GenericConversionService(), null); + BindConverter bindConverter = BindConverter.get(Collections.singletonList(new GenericConversionService()), + null); Duration result = bindConverter.convert("10s", ResolvableType.forClass(Duration.class)); assertThat(result.getSeconds()).isEqualTo(10); } private BindConverter getPropertyEditorOnlyBindConverter( Consumer propertyEditorInitializer) { - return BindConverter.get(new ThrowingConversionService(), propertyEditorInitializer); + return BindConverter.get(Collections.singletonList(new ThrowingConversionService()), propertyEditorInitializer); } private BindConverter getBindConverter(Converter converter) { GenericConversionService conversionService = new GenericConversionService(); conversionService.addConverter(converter); - return BindConverter.get(conversionService, null); + return BindConverter.get(Collections.singletonList(conversionService), null); } private void registerSampleTypeEditor(PropertyEditorRegistry registry) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java index 83f02ad58fe..c40a606d0dd 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -38,6 +38,7 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import org.springframework.boot.convert.Delimiter; import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConversionService; import org.springframework.format.annotation.DateTimeFormat; import static org.assertj.core.api.Assertions.assertThat; @@ -340,7 +341,7 @@ class JavaBeanBinderTests { @Test void bindToInstanceWhenNoDefaultConstructorShouldBind() { - Binder binder = new Binder(this.sources, null, null, null, null, + Binder binder = new Binder(this.sources, null, (ConversionService) null, null, null, (bindable, isNestedConstructorBinding) -> null); MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo.value", "bar"); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index aa6ce6a1747..7286c7c348e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -31,6 +31,7 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyN import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConversionService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.util.Assert; @@ -109,7 +110,7 @@ class ValueObjectBinderTests { this.sources.add(source); Constructor[] constructors = MultipleConstructorsBean.class.getDeclaredConstructors(); Constructor constructor = (constructors[0].getParameterCount() == 1) ? constructors[0] : constructors[1]; - Binder binder = new Binder(this.sources, null, null, null, null, + Binder binder = new Binder(this.sources, null, (ConversionService) null, null, null, (bindable, isNestedConstructorBinding) -> constructor); MultipleConstructorsBean bound = binder.bind("foo", Bindable.of(MultipleConstructorsBean.class)).get(); assertThat(bound.getIntValue()).isEqualTo(12);