Skip to content

Commit e92be5a

Browse files
committed
Support lambda based converters by parsing bean method signature generics
1 parent 61fb9f8 commit e92be5a

File tree

2 files changed

+402
-16
lines changed

2 files changed

+402
-16
lines changed

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java

+314-16
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,23 @@
1717
package org.springframework.boot.convert;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.util.Collections;
2021
import java.util.LinkedHashSet;
22+
import java.util.Map;
23+
import java.util.Optional;
2124
import java.util.Set;
2225

2326
import org.springframework.beans.factory.ListableBeanFactory;
27+
import org.springframework.beans.factory.config.BeanDefinition;
28+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
29+
import org.springframework.context.ConfigurableApplicationContext;
30+
import org.springframework.context.i18n.LocaleContextHolder;
31+
import org.springframework.core.GenericTypeResolver;
32+
import org.springframework.core.ResolvableType;
2433
import org.springframework.core.convert.ConversionService;
2534
import org.springframework.core.convert.TypeDescriptor;
35+
import org.springframework.core.convert.converter.ConditionalConverter;
36+
import org.springframework.core.convert.converter.ConditionalGenericConverter;
2637
import org.springframework.core.convert.converter.Converter;
2738
import org.springframework.core.convert.converter.ConverterFactory;
2839
import org.springframework.core.convert.converter.ConverterRegistry;
@@ -37,6 +48,7 @@
3748
import org.springframework.format.Printer;
3849
import org.springframework.format.support.DefaultFormattingConversionService;
3950
import org.springframework.format.support.FormattingConversionService;
51+
import org.springframework.util.StringUtils;
4052
import org.springframework.util.StringValueResolver;
4153

4254
/**
@@ -49,6 +61,7 @@
4961
* against registry instance.
5062
*
5163
* @author Phillip Webb
64+
* @author Shixiong Guo(viviel)
5265
* @since 2.0.0
5366
*/
5467
public class ApplicationConversionService extends FormattingConversionService {
@@ -272,28 +285,313 @@ public static void addApplicationFormatters(FormatterRegistry registry) {
272285
* @since 2.2.0
273286
*/
274287
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
275-
Set<Object> beans = new LinkedHashSet<>();
276-
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
277-
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
278-
beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
279-
beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
280-
for (Object bean : beans) {
281-
if (bean instanceof GenericConverter) {
282-
registry.addConverter((GenericConverter) bean);
288+
Set<Map.Entry<String, ?>> entries = new LinkedHashSet<>();
289+
entries.addAll(beanFactory.getBeansOfType(GenericConverter.class).entrySet());
290+
entries.addAll(beanFactory.getBeansOfType(Converter.class).entrySet());
291+
entries.addAll(beanFactory.getBeansOfType(Printer.class).entrySet());
292+
entries.addAll(beanFactory.getBeansOfType(Parser.class).entrySet());
293+
for (Map.Entry<String, ?> e : entries) {
294+
String beanName = e.getKey();
295+
Object bean = e.getValue();
296+
try {
297+
doAddBean(registry, bean);
283298
}
284-
else if (bean instanceof Converter) {
285-
registry.addConverter((Converter<?, ?>) bean);
299+
catch (IllegalArgumentException ex) {
300+
if (!tryAddFactoryMethodBean(registry, beanFactory, beanName, bean)) {
301+
throw ex;
302+
}
303+
}
304+
}
305+
}
306+
307+
private static void doAddBean(FormatterRegistry registry, Object bean) {
308+
if (bean instanceof GenericConverter) {
309+
registry.addConverter((GenericConverter) bean);
310+
}
311+
else if (bean instanceof Converter) {
312+
registry.addConverter((Converter<?, ?>) bean);
313+
}
314+
else if (bean instanceof Formatter) {
315+
registry.addFormatter((Formatter<?>) bean);
316+
}
317+
else if (bean instanceof Printer) {
318+
registry.addPrinter((Printer<?>) bean);
319+
}
320+
else if (bean instanceof Parser) {
321+
registry.addParser((Parser<?>) bean);
322+
}
323+
}
324+
325+
private static boolean tryAddFactoryMethodBean(FormatterRegistry registry, ListableBeanFactory beanFactory,
326+
String beanName, Object bean) {
327+
ConfigurableListableBeanFactory clbf = getConfigurableListableBeanFactory(beanFactory);
328+
if (clbf == null) {
329+
return false;
330+
}
331+
if (!isFactoryMethod(clbf, beanName)) {
332+
return false;
333+
}
334+
if (bean instanceof Converter) {
335+
return addConverter(registry, clbf, beanName, (Converter<?, ?>) bean);
336+
}
337+
else if (bean instanceof Printer) {
338+
return addPrinter(registry, clbf, beanName, (Printer<?>) bean);
339+
}
340+
else if (bean instanceof Parser) {
341+
return addParser(registry, clbf, beanName, (Parser<?>) bean);
342+
}
343+
return false;
344+
}
345+
346+
private static ConfigurableListableBeanFactory getConfigurableListableBeanFactory(ListableBeanFactory beanFactory) {
347+
ListableBeanFactory bf = beanFactory;
348+
if (bf instanceof ConfigurableApplicationContext) {
349+
bf = ((ConfigurableApplicationContext) bf).getBeanFactory();
350+
}
351+
if (bf instanceof ConfigurableListableBeanFactory) {
352+
return (ConfigurableListableBeanFactory) bf;
353+
}
354+
return null;
355+
}
356+
357+
private static boolean isFactoryMethod(ConfigurableListableBeanFactory clbf, String beanName) {
358+
BeanDefinition bd = clbf.getMergedBeanDefinition(beanName);
359+
return bd.getFactoryMethodName() != null;
360+
}
361+
362+
private static boolean addConverter(FormatterRegistry registry, ConfigurableListableBeanFactory beanFactory,
363+
String beanName, Converter<?, ?> converter) {
364+
ConverterAdapter adapter = getConverterAdapter(beanFactory, beanName, converter);
365+
if (adapter == null) {
366+
return false;
367+
}
368+
registry.addConverter(adapter);
369+
return true;
370+
}
371+
372+
private static ConverterAdapter getConverterAdapter(ConfigurableListableBeanFactory beanFactory, String beanName,
373+
Converter<?, ?> converter) {
374+
ResolvableType[] types = getResolvableType(beanFactory, beanName);
375+
if (types.length < 2) {
376+
return null;
377+
}
378+
return new ConverterAdapter(converter, types[0], types[1]);
379+
}
380+
381+
private static ResolvableType[] getResolvableType(ConfigurableListableBeanFactory beanFactory, String beanName) {
382+
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
383+
ResolvableType resolvableType = beanDefinition.getResolvableType();
384+
return resolvableType.getGenerics();
385+
}
386+
387+
private static boolean addPrinter(FormatterRegistry registry, ConfigurableListableBeanFactory beanFactory,
388+
String beanName, Printer<?> printer) {
389+
PrinterAdapter adapter = getPrinterAdapter(beanFactory, beanName, printer);
390+
if (adapter == null) {
391+
return false;
392+
}
393+
registry.addConverter(adapter);
394+
return true;
395+
}
396+
397+
private static PrinterAdapter getPrinterAdapter(ConfigurableListableBeanFactory beanFactory, String beanName,
398+
Printer<?> printer) {
399+
ResolvableType[] types = getResolvableType(beanFactory, beanName);
400+
if (types.length < 1) {
401+
return null;
402+
}
403+
ConversionService conversionService = beanFactory.getBean(ConversionService.class);
404+
return new PrinterAdapter(types[0].resolve(), printer, conversionService);
405+
}
406+
407+
private static boolean addParser(FormatterRegistry registry, ConfigurableListableBeanFactory beanFactory,
408+
String beanName, Parser<?> parser) {
409+
ParserAdapter adapter = getParserAdapter(beanFactory, beanName, parser);
410+
if (adapter == null) {
411+
return false;
412+
}
413+
registry.addConverter(adapter);
414+
return true;
415+
}
416+
417+
private static ParserAdapter getParserAdapter(ConfigurableListableBeanFactory beanFactory, String beanName,
418+
Parser<?> parser) {
419+
ResolvableType[] types = getResolvableType(beanFactory, beanName);
420+
if (types.length < 1) {
421+
return null;
422+
}
423+
ConversionService conversionService = beanFactory.getBean(ConversionService.class);
424+
return new ParserAdapter(types[0].resolve(), parser, conversionService);
425+
}
426+
427+
/**
428+
* Adapts a {@link Converter} to a {@link GenericConverter}.
429+
* <p>
430+
* Reference from
431+
* {@link org.springframework.core.convert.support.GenericConversionService.ConverterAdapter}
432+
*/
433+
@SuppressWarnings("unchecked")
434+
private static final class ConverterAdapter implements ConditionalGenericConverter {
435+
436+
private final Converter<Object, Object> converter;
437+
438+
private final ConvertiblePair typeInfo;
439+
440+
private final ResolvableType targetType;
441+
442+
ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) {
443+
this.converter = (Converter<Object, Object>) converter;
444+
this.typeInfo = new ConvertiblePair(sourceType.toClass(), targetType.toClass());
445+
this.targetType = targetType;
446+
}
447+
448+
@Override
449+
public Set<ConvertiblePair> getConvertibleTypes() {
450+
return Collections.singleton(this.typeInfo);
451+
}
452+
453+
@Override
454+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
455+
// Check raw type first...
456+
if (this.typeInfo.getTargetType() != targetType.getObjectType()) {
457+
return false;
286458
}
287-
else if (bean instanceof Formatter) {
288-
registry.addFormatter((Formatter<?>) bean);
459+
// Full check for complex generic type match required?
460+
ResolvableType rt = targetType.getResolvableType();
461+
if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType)
462+
&& !this.targetType.hasUnresolvableGenerics()) {
463+
return false;
289464
}
290-
else if (bean instanceof Printer) {
291-
registry.addPrinter((Printer<?>) bean);
465+
return !(this.converter instanceof ConditionalConverter)
466+
|| ((ConditionalConverter) this.converter).matches(sourceType, targetType);
467+
}
468+
469+
@Override
470+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
471+
if (source == null) {
472+
return convertNullSource(sourceType, targetType);
292473
}
293-
else if (bean instanceof Parser) {
294-
registry.addParser((Parser<?>) bean);
474+
return this.converter.convert(source);
475+
}
476+
477+
@Override
478+
public String toString() {
479+
return (this.typeInfo + " : " + this.converter);
480+
}
481+
482+
/**
483+
* Template method to convert a {@code null} source.
484+
* <p>
485+
* The default implementation returns {@code null} or the Java 8
486+
* {@link java.util.Optional#empty()} instance if the target type is
487+
* {@code java.util.Optional}. Subclasses may override this to return custom
488+
* {@code null} objects for specific target types.
489+
* @param sourceType the source type to convert from
490+
* @param targetType the target type to convert to
491+
* @return the converted null object
492+
*/
493+
private Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) {
494+
if (targetType.getObjectType() == Optional.class) {
495+
return Optional.empty();
295496
}
497+
return null;
498+
}
499+
500+
}
501+
502+
private static class PrinterAdapter implements GenericConverter {
503+
504+
private final Class<?> fieldType;
505+
506+
private final TypeDescriptor printerObjectType;
507+
508+
@SuppressWarnings("rawtypes")
509+
private final Printer printer;
510+
511+
private final ConversionService conversionService;
512+
513+
PrinterAdapter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) {
514+
this.fieldType = fieldType;
515+
this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
516+
this.printer = printer;
517+
this.conversionService = conversionService;
518+
}
519+
520+
@Override
521+
public Set<ConvertiblePair> getConvertibleTypes() {
522+
return Collections.singleton(new ConvertiblePair(this.fieldType, String.class));
296523
}
524+
525+
@Override
526+
@SuppressWarnings("unchecked")
527+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
528+
if (!sourceType.isAssignableTo(this.printerObjectType)) {
529+
source = this.conversionService.convert(source, sourceType, this.printerObjectType);
530+
}
531+
if (source == null) {
532+
return "";
533+
}
534+
return this.printer.print(source, LocaleContextHolder.getLocale());
535+
}
536+
537+
private Class<?> resolvePrinterObjectType(Printer<?> printer) {
538+
return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class);
539+
}
540+
541+
@Override
542+
public String toString() {
543+
return (this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer);
544+
}
545+
546+
}
547+
548+
private static class ParserAdapter implements GenericConverter {
549+
550+
private final Class<?> fieldType;
551+
552+
private final Parser<?> parser;
553+
554+
private final ConversionService conversionService;
555+
556+
ParserAdapter(Class<?> fieldType, Parser<?> parser, ConversionService conversionService) {
557+
this.fieldType = fieldType;
558+
this.parser = parser;
559+
this.conversionService = conversionService;
560+
}
561+
562+
@Override
563+
public Set<ConvertiblePair> getConvertibleTypes() {
564+
return Collections.singleton(new ConvertiblePair(String.class, this.fieldType));
565+
}
566+
567+
@Override
568+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
569+
String text = (String) source;
570+
if (!StringUtils.hasText(text)) {
571+
return null;
572+
}
573+
Object result;
574+
try {
575+
result = this.parser.parse(text, LocaleContextHolder.getLocale());
576+
}
577+
catch (IllegalArgumentException ex) {
578+
throw ex;
579+
}
580+
catch (Throwable ex) {
581+
throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
582+
}
583+
TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass());
584+
if (!resultType.isAssignableTo(targetType)) {
585+
result = this.conversionService.convert(result, resultType, targetType);
586+
}
587+
return result;
588+
}
589+
590+
@Override
591+
public String toString() {
592+
return (String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser);
593+
}
594+
297595
}
298596

299597
}

0 commit comments

Comments
 (0)