diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java index e3db0c096ee6..a0071834cf3b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java @@ -16,11 +16,21 @@ package org.springframework.boot.convert; +import java.util.Collections; import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalConverter; +import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.GenericConverter; @@ -44,6 +54,7 @@ * against registry instance. * * @author Phillip Webb + * @author 郭世雄 Viviel * @since 2.0.0 */ public class ApplicationConversionService extends FormattingConversionService { @@ -157,17 +168,30 @@ public static void addApplicationFormatters(FormatterRegistry registry) { * @since 2.2.0 */ public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) { - Set beans = new LinkedHashSet<>(); - beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values()); - beans.addAll(beanFactory.getBeansOfType(Converter.class).values()); - beans.addAll(beanFactory.getBeansOfType(Printer.class).values()); - beans.addAll(beanFactory.getBeansOfType(Parser.class).values()); - for (Object bean : beans) { + Set> entries = new LinkedHashSet<>(); + entries.addAll(beanFactory.getBeansOfType(GenericConverter.class).entrySet()); + entries.addAll(beanFactory.getBeansOfType(Converter.class).entrySet()); + entries.addAll(beanFactory.getBeansOfType(Printer.class).entrySet()); + entries.addAll(beanFactory.getBeansOfType(Parser.class).entrySet()); + for (Map.Entry entity : entries) { + Object bean = entity.getValue(); if (bean instanceof GenericConverter) { registry.addConverter((GenericConverter) bean); } else if (bean instanceof Converter) { - registry.addConverter((Converter) bean); + if (beanFactory instanceof ConfigurableListableBeanFactory) { + ConverterAdapter adapter = getConverterAdapter((ConfigurableListableBeanFactory) beanFactory, + entity); + if (!Objects.isNull(adapter)) { + registry.addConverter(adapter); + } + else { + registry.addConverter((Converter) bean); + } + } + else { + registry.addConverter((Converter) bean); + } } else if (bean instanceof Formatter) { registry.addFormatter((Formatter) bean); @@ -181,4 +205,87 @@ else if (bean instanceof Parser) { } } + private static ConverterAdapter getConverterAdapter(ConfigurableListableBeanFactory beanFactory, + Map.Entry beanEntity) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanEntity.getKey()); + ResolvableType resolvableType = beanDefinition.getResolvableType(); + ResolvableType[] types = resolvableType.getGenerics(); + if (types.length < 2) { + return null; + } + return new ConverterAdapter((Converter) beanEntity.getValue(), types[0], types[1]); + } + + /** + * Adapts a {@link Converter} to a {@link GenericConverter}. + */ + @SuppressWarnings("unchecked") + private static final class ConverterAdapter implements ConditionalGenericConverter { + + private final Converter converter; + + private final ConvertiblePair typeInfo; + + private final ResolvableType targetType; + + ConverterAdapter(Converter converter, ResolvableType sourceType, ResolvableType targetType) { + this.converter = (Converter) converter; + this.typeInfo = new ConvertiblePair(sourceType.toClass(), targetType.toClass()); + this.targetType = targetType; + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(this.typeInfo); + } + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + // Check raw type first... + if (this.typeInfo.getTargetType() != targetType.getObjectType()) { + return false; + } + // Full check for complex generic type match required? + ResolvableType rt = targetType.getResolvableType(); + if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) + && !this.targetType.hasUnresolvableGenerics()) { + return false; + } + return !(this.converter instanceof ConditionalConverter) + || ((ConditionalConverter) this.converter).matches(sourceType, targetType); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (source == null) { + return convertNullSource(sourceType, targetType); + } + return this.converter.convert(source); + } + + @Override + public String toString() { + return (this.typeInfo + " : " + this.converter); + } + + /** + * Template method to convert a {@code null} source. + *

+ * The default implementation returns {@code null} or the Java 8 + * {@link java.util.Optional#empty()} instance if the target type is + * {@code java.util.Optional}. Subclasses may override this to return custom + * {@code null} objects for specific target types. + * @param sourceType the source type to convert from + * @param targetType the target type to convert to + * @return the converted null object + */ + private Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) { + if (targetType.getObjectType() == Optional.class) { + return Optional.empty(); + } + return null; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java index d2aa34ae5b7f..add54373b1c5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java @@ -24,6 +24,8 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -64,6 +66,26 @@ void addBeansWhenHasConverterBeanAddConverter() { } } + @Test + void addBeansWhenHasLambdaConverterBeanAddConverter() { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + ExampleLambdaConverter.class)) { + ApplicationConversionService.addBeans(this.registry, context); + verify(this.registry).addConverter(context.getBean(Converter.class)); + verifyNoMoreInteractions(this.registry); + } + } + + @Test + void addBeansWhenHasMethodReferenceConverterBeanAddConverter() { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + ExampleMethodReferenceConverter.class)) { + ApplicationConversionService.addBeans(this.registry, context); + verify(this.registry).addConverter(context.getBean(Converter.class)); + verifyNoMoreInteractions(this.registry); + } + } + @Test void addBeansWhenHasFormatterBeanAddsOnlyFormatter() { try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExampleFormatter.class)) { @@ -114,6 +136,26 @@ public Integer convert(String source) { } + @Configuration + static class ExampleLambdaConverter { + + @Bean + public Converter converter() { + return e -> Integer.valueOf(e); + } + + } + + @Configuration + static class ExampleMethodReferenceConverter { + + @Bean + public Converter converter() { + return Integer::valueOf; + } + + } + static class ExampleFormatter implements Formatter { @Override