Skip to content

Commit

Permalink
Support lambda based converters by parsing bean method signature gene…
Browse files Browse the repository at this point in the history
…rics
  • Loading branch information
viviel committed Mar 8, 2021
1 parent 043ca4d commit 1e793fe
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,22 @@

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.context.ConfigurableApplicationContext;
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;
Expand All @@ -46,6 +56,7 @@
* against registry instance.
*
* @author Phillip Webb
* @author 郭 世雄
* @since 2.0.0
*/
public class ApplicationConversionService extends FormattingConversionService {
Expand Down Expand Up @@ -188,17 +199,18 @@ public static void addApplicationFormatters(FormatterRegistry registry) {
* @since 2.2.0
*/
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
Set<Object> 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<Map.Entry<String, ?>> 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<String, ?> entity : entries) {
Object bean = entity.getValue();
if (bean instanceof GenericConverter) {
registry.addConverter((GenericConverter) bean);
}
else if (bean instanceof Converter) {
registry.addConverter((Converter<?, ?>) bean);
addConverter(registry, beanFactory, entity);
}
else if (bean instanceof Formatter) {
registry.addFormatter((Formatter<?>) bean);
Expand All @@ -212,4 +224,120 @@ else if (bean instanceof Parser) {
}
}

private static void addConverter(FormatterRegistry registry, ListableBeanFactory beanFactory,
Map.Entry<String, ?> entity) {
try {
registry.addConverter((Converter<?, ?>) entity.getValue());
}
catch (IllegalArgumentException ex) {
boolean success = tryAddMethodSignatureGenericsConverter(registry, beanFactory, entity);
if (!success) {
throw ex;
}
}
}

private static boolean tryAddMethodSignatureGenericsConverter(FormatterRegistry registry,
ListableBeanFactory beanFactory, Map.Entry<String, ?> entity) {
ListableBeanFactory bf = beanFactory;
if (bf instanceof ConfigurableApplicationContext) {
bf = ((ConfigurableApplicationContext) bf).getBeanFactory();
}
if (bf instanceof ConfigurableListableBeanFactory) {
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) bf;
ConverterAdapter adapter = getConverterAdapter(clbf, entity);
if (!Objects.isNull(adapter)) {
registry.addConverter(adapter);
return true;
}
}
return false;
}

private static ConverterAdapter getConverterAdapter(ConfigurableListableBeanFactory beanFactory,
Map.Entry<String, ?> beanEntity) {
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(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}.
* <p>
* Reference from
* {@link org.springframework.core.convert.support.GenericConversionService.ConverterAdapter}
*/
@SuppressWarnings("unchecked")
private static final class ConverterAdapter implements ConditionalGenericConverter {

private final Converter<Object, Object> converter;

private final ConvertiblePair typeInfo;

private final ResolvableType targetType;

ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) {
this.converter = (Converter<Object, Object>) converter;
this.typeInfo = new ConvertiblePair(sourceType.toClass(), targetType.toClass());
this.targetType = targetType;
}

@Override
public Set<ConvertiblePair> 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.
* <p>
* 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;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.format.Formatter;
Expand All @@ -34,6 +36,8 @@
import org.springframework.format.Printer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
Expand All @@ -42,6 +46,7 @@
* Tests for {@link ApplicationConversionService}.
*
* @author Phillip Webb
* @author 郭 世雄
*/
class ApplicationConversionServiceTests {

Expand All @@ -66,6 +71,30 @@ void addBeansWhenHasConverterBeanAddConverter() {
}
}

@Test
void addBeansWhenHasLambdaConverterBeanAddConverter() {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
ExampleLambdaConverter.class)) {
willThrow(IllegalArgumentException.class).given(this.registry).addConverter(isA(Converter.class));
ApplicationConversionService.addBeans(this.registry, context);
verify(this.registry).addConverter(context.getBean(Converter.class));
verify(this.registry).addConverter(isA(ConditionalGenericConverter.class));
verifyNoMoreInteractions(this.registry);
}
}

@Test
void addBeansWhenHasMethodReferenceConverterBeanAddConverter() {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
ExampleMethodReferenceConverter.class)) {
willThrow(IllegalArgumentException.class).given(this.registry).addConverter(isA(Converter.class));
ApplicationConversionService.addBeans(this.registry, context);
verify(this.registry).addConverter(context.getBean(Converter.class));
verify(this.registry).addConverter(isA(ConditionalGenericConverter.class));
verifyNoMoreInteractions(this.registry);
}
}

@Test
void addBeansWhenHasFormatterBeanAddsOnlyFormatter() {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExampleFormatter.class)) {
Expand Down Expand Up @@ -136,6 +165,24 @@ public Integer convert(String source) {

}

static class ExampleLambdaConverter {

@Bean
Converter<String, Integer> converter() {
return (e) -> Integer.valueOf(e);
}

}

static class ExampleMethodReferenceConverter {

@Bean
Converter<String, Integer> converter() {
return Integer::valueOf;
}

}

static class ExampleFormatter implements Formatter<Integer> {

@Override
Expand Down

0 comments on commit 1e793fe

Please sign in to comment.