Skip to content

Commit 4eae8a0

Browse files
committed
Merge pull request #22885 from viviel
* pr/22885: Polish 'Support lambda based converters via bean method signature generics' Support lambda based converters via bean method signature generics Closes gh-22885
2 parents 1bee5f3 + dcb2dd5 commit 4eae8a0

File tree

2 files changed

+521
-24
lines changed

2 files changed

+521
-24
lines changed

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

Lines changed: 288 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,12 +17,24 @@
1717
package org.springframework.boot.convert;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.LinkedHashSet;
20+
import java.util.LinkedHashMap;
21+
import java.util.Map;
22+
import java.util.Optional;
2123
import java.util.Set;
24+
import java.util.function.BiFunction;
25+
import java.util.function.Consumer;
26+
import java.util.function.Supplier;
2227

2328
import org.springframework.beans.factory.ListableBeanFactory;
29+
import org.springframework.beans.factory.config.BeanDefinition;
30+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
31+
import org.springframework.context.ConfigurableApplicationContext;
32+
import org.springframework.context.i18n.LocaleContextHolder;
33+
import org.springframework.core.ResolvableType;
2434
import org.springframework.core.convert.ConversionService;
2535
import org.springframework.core.convert.TypeDescriptor;
36+
import org.springframework.core.convert.converter.ConditionalConverter;
37+
import org.springframework.core.convert.converter.ConditionalGenericConverter;
2638
import org.springframework.core.convert.converter.Converter;
2739
import org.springframework.core.convert.converter.ConverterFactory;
2840
import org.springframework.core.convert.converter.ConverterRegistry;
@@ -37,6 +49,8 @@
3749
import org.springframework.format.Printer;
3850
import org.springframework.format.support.DefaultFormattingConversionService;
3951
import org.springframework.format.support.FormattingConversionService;
52+
import org.springframework.util.Assert;
53+
import org.springframework.util.StringUtils;
4054
import org.springframework.util.StringValueResolver;
4155

4256
/**
@@ -49,10 +63,13 @@
4963
* against registry instance.
5064
*
5165
* @author Phillip Webb
66+
* @author Shixiong Guo
5267
* @since 2.0.0
5368
*/
5469
public class ApplicationConversionService extends FormattingConversionService {
5570

71+
private static final ResolvableType STRING = ResolvableType.forClass(String.class);
72+
5673
private static volatile ApplicationConversionService sharedInstance;
5774

5875
private final boolean unmodifiable;
@@ -265,35 +282,284 @@ public static void addApplicationFormatters(FormatterRegistry registry) {
265282
}
266283

267284
/**
268-
* Add {@link GenericConverter}, {@link Converter}, {@link Printer}, {@link Parser}
269-
* and {@link Formatter} beans from the specified context.
285+
* Add {@link Printer}, {@link Parser}, {@link Formatter}, {@link Converter},
286+
* {@link ConverterFactory}, {@link GenericConverter}, and beans from the specified
287+
* bean factory.
270288
* @param registry the service to register beans with
271289
* @param beanFactory the bean factory to get the beans from
272290
* @since 2.2.0
273291
*/
274292
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 genericConverter) {
282-
registry.addConverter(genericConverter);
283-
}
284-
else if (bean instanceof Converter<?, ?> converter) {
285-
registry.addConverter(converter);
286-
}
287-
else if (bean instanceof Formatter<?> formatter) {
288-
registry.addFormatter(formatter);
293+
ConfigurableListableBeanFactory configurableBeanFactory = getConfigurableListableBeanFactory(beanFactory);
294+
getBeans(beanFactory).forEach((beanName, bean) -> {
295+
BeanDefinition beanDefinition = (configurableBeanFactory != null)
296+
? configurableBeanFactory.getMergedBeanDefinition(beanName) : null;
297+
ResolvableType type = (beanDefinition != null) ? beanDefinition.getResolvableType() : null;
298+
addBean(registry, bean, type);
299+
});
300+
}
301+
302+
private static ConfigurableListableBeanFactory getConfigurableListableBeanFactory(ListableBeanFactory beanFactory) {
303+
if (beanFactory instanceof ConfigurableApplicationContext applicationContext) {
304+
return applicationContext.getBeanFactory();
305+
}
306+
if (beanFactory instanceof ConfigurableListableBeanFactory configurableListableBeanFactory) {
307+
return configurableListableBeanFactory;
308+
}
309+
return null;
310+
}
311+
312+
private static Map<String, Object> getBeans(ListableBeanFactory beanFactory) {
313+
Map<String, Object> beans = new LinkedHashMap<>();
314+
beans.putAll(getBeans(beanFactory, Printer.class));
315+
beans.putAll(getBeans(beanFactory, Parser.class));
316+
beans.putAll(getBeans(beanFactory, Formatter.class));
317+
beans.putAll(getBeans(beanFactory, Converter.class));
318+
beans.putAll(getBeans(beanFactory, ConverterFactory.class));
319+
beans.putAll(getBeans(beanFactory, GenericConverter.class));
320+
return beans;
321+
}
322+
323+
private static <T> Map<String, T> getBeans(ListableBeanFactory beanFactory, Class<T> type) {
324+
return beanFactory.getBeansOfType(type);
325+
}
326+
327+
static void addBean(FormatterRegistry registry, Object bean, ResolvableType beanType) {
328+
if (bean instanceof GenericConverter converterBean) {
329+
addBean(registry, converterBean, beanType, GenericConverter.class, registry::addConverter, (Runnable) null);
330+
}
331+
else if (bean instanceof Converter<?, ?> converterBean) {
332+
addBean(registry, converterBean, beanType, Converter.class, registry::addConverter,
333+
ConverterBeanAdapter::new);
334+
}
335+
else if (bean instanceof ConverterFactory<?, ?> converterBean) {
336+
addBean(registry, converterBean, beanType, ConverterFactory.class, registry::addConverterFactory,
337+
ConverterFactoryBeanAdapter::new);
338+
}
339+
else if (bean instanceof Formatter<?> formatterBean) {
340+
addBean(registry, formatterBean, beanType, Formatter.class, registry::addFormatter, () -> {
341+
registry.addConverter(new PrinterBeanAdapter(formatterBean, beanType));
342+
registry.addConverter(new ParserBeanAdapter(formatterBean, beanType));
343+
});
344+
}
345+
else if (bean instanceof Printer<?> printerBean) {
346+
addBean(registry, printerBean, beanType, Printer.class, registry::addPrinter, PrinterBeanAdapter::new);
347+
}
348+
else if (bean instanceof Parser<?> parserBean) {
349+
addBean(registry, parserBean, beanType, Parser.class, registry::addParser, ParserBeanAdapter::new);
350+
}
351+
}
352+
353+
private static <B, T> void addBean(FormatterRegistry registry, B bean, ResolvableType beanType, Class<T> type,
354+
Consumer<B> standardRegistrar, BiFunction<B, ResolvableType, BeanAdapter<?>> beanAdapterFactory) {
355+
addBean(registry, bean, beanType, type, standardRegistrar,
356+
() -> registry.addConverter(beanAdapterFactory.apply(bean, beanType)));
357+
}
358+
359+
private static <B, T> void addBean(FormatterRegistry registry, B bean, ResolvableType beanType, Class<T> type,
360+
Consumer<B> standardRegistrar, Runnable beanAdapterRegistrar) {
361+
if (beanType != null && beanAdapterRegistrar != null
362+
&& ResolvableType.forInstance(bean).as(type).hasUnresolvableGenerics()) {
363+
beanAdapterRegistrar.run();
364+
return;
365+
}
366+
standardRegistrar.accept(bean);
367+
}
368+
369+
/**
370+
* Base class for adapters that adapt a bean to a {@link GenericConverter}.
371+
*
372+
* @param <B> the base type of the bean
373+
*/
374+
abstract static class BeanAdapter<B> implements ConditionalGenericConverter {
375+
376+
private final B bean;
377+
378+
private final ResolvableTypePair types;
379+
380+
BeanAdapter(B bean, ResolvableType beanType) {
381+
Assert.isInstanceOf(beanType.toClass(), bean);
382+
ResolvableType type = ResolvableType.forClass(getClass()).as(BeanAdapter.class).getGeneric();
383+
ResolvableType[] generics = beanType.as(type.toClass()).getGenerics();
384+
this.bean = bean;
385+
this.types = getResolvableTypePair(generics);
386+
}
387+
388+
protected ResolvableTypePair getResolvableTypePair(ResolvableType[] generics) {
389+
return new ResolvableTypePair(generics[0], generics[1]);
390+
}
391+
392+
protected B bean() {
393+
return this.bean;
394+
}
395+
396+
@Override
397+
public Set<ConvertiblePair> getConvertibleTypes() {
398+
return Set.of(new ConvertiblePair(this.types.source().toClass(), this.types.target().toClass()));
399+
}
400+
401+
@Override
402+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
403+
return (this.types.target().toClass() == targetType.getObjectType()
404+
&& matchesTargetType(targetType.getResolvableType()));
405+
}
406+
407+
private boolean matchesTargetType(ResolvableType targetType) {
408+
ResolvableType ours = this.types.target();
409+
return targetType.getType() instanceof Class || targetType.isAssignableFrom(ours)
410+
|| this.types.target().hasUnresolvableGenerics();
411+
}
412+
413+
protected final boolean conditionalConverterCandidateMatches(Object conditionalConverterCandidate,
414+
TypeDescriptor sourceType, TypeDescriptor targetType) {
415+
return (conditionalConverterCandidate instanceof ConditionalConverter conditionalConverter)
416+
? conditionalConverter.matches(sourceType, targetType) : true;
417+
}
418+
419+
@SuppressWarnings({ "unchecked", "rawtypes" })
420+
protected final Object convert(Object source, TypeDescriptor targetType, Converter<?, ?> converter) {
421+
return (source != null) ? ((Converter) converter).convert(source) : convertNull(targetType);
422+
}
423+
424+
private Object convertNull(TypeDescriptor targetType) {
425+
return (targetType.getObjectType() != Optional.class) ? null : Optional.empty();
426+
}
427+
428+
@Override
429+
public String toString() {
430+
return this.types + " : " + this.bean;
431+
}
432+
433+
}
434+
435+
/**
436+
* Adapts a {@link Printer} bean to a {@link GenericConverter}.
437+
*/
438+
static class PrinterBeanAdapter extends BeanAdapter<Printer<?>> {
439+
440+
PrinterBeanAdapter(Printer<?> bean, ResolvableType beanType) {
441+
super(bean, beanType);
442+
}
443+
444+
@Override
445+
protected ResolvableTypePair getResolvableTypePair(ResolvableType[] generics) {
446+
return new ResolvableTypePair(generics[0], STRING);
447+
}
448+
449+
@Override
450+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
451+
return (source != null) ? print(source) : "";
452+
}
453+
454+
@SuppressWarnings("unchecked")
455+
private String print(Object object) {
456+
return ((Printer<Object>) bean()).print(object, LocaleContextHolder.getLocale());
457+
}
458+
459+
}
460+
461+
/**
462+
* Adapts a {@link Parser} bean to a {@link GenericConverter}.
463+
*/
464+
static class ParserBeanAdapter extends BeanAdapter<Parser<?>> {
465+
466+
ParserBeanAdapter(Parser<?> bean, ResolvableType beanType) {
467+
super(bean, beanType);
468+
}
469+
470+
@Override
471+
protected ResolvableTypePair getResolvableTypePair(ResolvableType[] generics) {
472+
return new ResolvableTypePair(STRING, generics[0]);
473+
}
474+
475+
@Override
476+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
477+
String text = (String) source;
478+
return (!StringUtils.hasText(text)) ? null : parse(text);
479+
}
480+
481+
private Object parse(String text) {
482+
try {
483+
return bean().parse(text, LocaleContextHolder.getLocale());
289484
}
290-
else if (bean instanceof Printer<?> printer) {
291-
registry.addPrinter(printer);
485+
catch (IllegalArgumentException ex) {
486+
throw ex;
292487
}
293-
else if (bean instanceof Parser<?> parser) {
294-
registry.addParser(parser);
488+
catch (Throwable ex) {
489+
throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
295490
}
296491
}
492+
493+
}
494+
495+
/**
496+
* Adapts a {@link Converter} bean to a {@link GenericConverter}.
497+
*/
498+
static final class ConverterBeanAdapter extends BeanAdapter<Converter<?, ?>> {
499+
500+
ConverterBeanAdapter(Converter<?, ?> bean, ResolvableType beanType) {
501+
super(bean, beanType);
502+
}
503+
504+
@Override
505+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
506+
return super.matches(sourceType, targetType)
507+
&& conditionalConverterCandidateMatches(bean(), sourceType, targetType);
508+
}
509+
510+
@Override
511+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
512+
return convert(source, targetType, bean());
513+
}
514+
515+
}
516+
517+
/**
518+
* Adapts a {@link ConverterFactory} bean to a {@link GenericConverter}.
519+
*/
520+
private static final class ConverterFactoryBeanAdapter extends BeanAdapter<ConverterFactory<?, ?>> {
521+
522+
ConverterFactoryBeanAdapter(ConverterFactory<?, ?> bean, ResolvableType beanType) {
523+
super(bean, beanType);
524+
}
525+
526+
@Override
527+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
528+
return super.matches(sourceType, targetType)
529+
&& conditionalConverterCandidateMatches(bean(), sourceType, targetType)
530+
&& conditionalConverterCandidateMatches(getConverter(targetType::getType), sourceType, targetType);
531+
}
532+
533+
@Override
534+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
535+
return convert(source, targetType, getConverter(targetType::getObjectType));
536+
}
537+
538+
@SuppressWarnings({ "unchecked", "rawtypes" })
539+
private Converter<Object, ?> getConverter(Supplier<Class<?>> typeSupplier) {
540+
return ((ConverterFactory) bean()).getConverter(typeSupplier.get());
541+
}
542+
543+
}
544+
545+
/**
546+
* Convertible type information as extracted from bean generics.
547+
*
548+
* @param source the source type
549+
* @param target the target type
550+
*/
551+
record ResolvableTypePair(ResolvableType source, ResolvableType target) {
552+
553+
ResolvableTypePair {
554+
Assert.notNull(source.resolve(), "'source' cannot be resolved");
555+
Assert.notNull(target.resolve(), "'target' cannot be resolved");
556+
}
557+
558+
@Override
559+
public final String toString() {
560+
return source() + " -> " + target();
561+
}
562+
297563
}
298564

299565
}

0 commit comments

Comments
 (0)