Support binding with BeanFactory ConversionService
Update `BindConverter` so that multiple `ConverterServices` can be specified when binding. This change allows `ConversionServiceDeducer` to add both the `BeanFactory` conversion service as well as a custom `ApplicationConversionService` when beans annotated with `@ConfigurationPropertiesBinding` are found. Fixes gh-26089
This commit is contained in:
@@ -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<ConversionService> getConversionServices() {
|
||||
return new ConversionServiceDeducer(this.applicationContext).getConversionServices();
|
||||
}
|
||||
|
||||
private Consumer<PropertyEditorRegistry> getPropertyEditorInitializer() {
|
||||
|
||||
@@ -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<ConversionService> 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<ConversionService> getConversionServices(ConfigurableApplicationContext applicationContext) {
|
||||
List<ConversionService> 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<Converter> converters;
|
||||
@@ -66,17 +87,11 @@ class ConversionServiceDeducer {
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final List<Formatter> 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 <T> List<T> beans(BeanFactory beanFactory, Class<T> 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 <T> List<T> beans(Class<T> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,64 +49,83 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
final class BindConverter {
|
||||
|
||||
private static final Set<Class<?>> EXCLUDED_EDITORS;
|
||||
static {
|
||||
Set<Class<?>> 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<ConversionService> delegates;
|
||||
|
||||
private BindConverter(ConversionService conversionService,
|
||||
private BindConverter(List<ConversionService> conversionServices,
|
||||
Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
List<ConversionService> conversionServices = getConversionServices(conversionService,
|
||||
propertyEditorInitializer);
|
||||
this.conversionService = new CompositeConversionService(conversionServices);
|
||||
}
|
||||
|
||||
private List<ConversionService> getConversionServices(ConversionService conversionService,
|
||||
Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
|
||||
List<ConversionService> services = new ArrayList<>();
|
||||
services.add(new TypeConverterConversionService(propertyEditorInitializer));
|
||||
services.add(conversionService);
|
||||
if (!(conversionService instanceof ApplicationConversionService)) {
|
||||
services.add(ApplicationConversionService.getSharedInstance());
|
||||
List<ConversionService> 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;
|
||||
}
|
||||
}
|
||||
return services;
|
||||
if (!hasApplication) {
|
||||
delegates.add(ApplicationConversionService.getSharedInstance());
|
||||
}
|
||||
this.delegates = Collections.unmodifiableList(delegates);
|
||||
}
|
||||
|
||||
boolean canConvert(Object value, ResolvableType type, Annotation... annotations) {
|
||||
return this.conversionService.canConvert(TypeDescriptor.forObject(value),
|
||||
new ResolvableTypeDescriptor(type, annotations));
|
||||
boolean canConvert(Object source, ResolvableType targetType, Annotation... targetAnnotations) {
|
||||
return canConvert(TypeDescriptor.forObject(source),
|
||||
new ResolvableTypeDescriptor(targetType, targetAnnotations));
|
||||
}
|
||||
|
||||
<T> T convert(Object result, Bindable<T> target) {
|
||||
return convert(result, target.getType(), target.getAnnotations());
|
||||
private boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
for (ConversionService service : this.delegates) {
|
||||
if (service.canConvert(sourceType, targetType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
<T> T convert(Object source, Bindable<T> target) {
|
||||
return convert(source, target.getType(), target.getAnnotations());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> T convert(Object value, ResolvableType type, Annotation... annotations) {
|
||||
if (value == null) {
|
||||
<T> 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<PropertyEditorRegistry> 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);
|
||||
}
|
||||
|
||||
static BindConverter get(List<ConversionService> conversionServices,
|
||||
Consumer<PropertyEditorRegistry> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,58 +139,6 @@ final class BindConverter {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Composite {@link ConversionService} used to call multiple services.
|
||||
*/
|
||||
static class CompositeConversionService implements ConversionService {
|
||||
|
||||
private final List<ConversionService> delegates;
|
||||
|
||||
CompositeConversionService(List<ConversionService> 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> T convert(Object source, Class<T> targetType) {
|
||||
Assert.notNull(targetType, "Target type to convert to cannot be null");
|
||||
return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ConversionService} implementation that delegates to a
|
||||
* {@link SimpleTypeConverter}. Allows {@link PropertyEditor} based conversion for
|
||||
@@ -208,6 +175,13 @@ final class BindConverter {
|
||||
*/
|
||||
private static class TypeConverterConverter implements ConditionalGenericConverter {
|
||||
|
||||
private static final Set<Class<?>> EXCLUDED_EDITORS;
|
||||
static {
|
||||
Set<Class<?>> excluded = new HashSet<>();
|
||||
excluded.add(FileEditor.class); // gh-12163
|
||||
EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded);
|
||||
}
|
||||
|
||||
private final SimpleTypeConverter typeConverter;
|
||||
|
||||
TypeConverterConverter(SimpleTypeConverter typeConverter) {
|
||||
|
||||
@@ -60,9 +60,7 @@ public class Binder {
|
||||
|
||||
private final PlaceholdersResolver placeholdersResolver;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private final Consumer<PropertyEditorRegistry> propertyEditorInitializer;
|
||||
private final BindConverter bindConverter;
|
||||
|
||||
private final BindHandler defaultBindHandler;
|
||||
|
||||
@@ -159,12 +157,34 @@ public class Binder {
|
||||
public Binder(Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver,
|
||||
ConversionService conversionService, Consumer<PropertyEditorRegistry> propertyEditorInitializer,
|
||||
BindHandler defaultBindHandler, BindConstructorProvider constructorProvider) {
|
||||
this(sources, placeholdersResolver,
|
||||
(conversionService != null) ? Collections.singletonList(conversionService)
|
||||
: (List<ConversionService>) 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<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver,
|
||||
List<ConversionService> conversionServices, Consumer<PropertyEditorRegistry> 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<ConfigurationPropertySource> 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
|
||||
|
||||
@@ -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<String, Alien> {
|
||||
|
||||
@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;
|
||||
|
||||
@@ -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<ConversionService> 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<ConversionService> 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)
|
||||
|
||||
@@ -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<PropertyEditorRegistry> 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<PropertyEditorRegistry> 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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user