Skip to content

Commit 1e793fe

Browse files
committed
Support lambda based converters by parsing bean method signature generics
1 parent 043ca4d commit 1e793fe

File tree

2 files changed

+182
-7
lines changed

2 files changed

+182
-7
lines changed

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

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,22 @@
1616

1717
package org.springframework.boot.convert;
1818

19+
import java.util.Collections;
1920
import java.util.LinkedHashSet;
21+
import java.util.Map;
22+
import java.util.Objects;
23+
import java.util.Optional;
2024
import java.util.Set;
2125

2226
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.core.ResolvableType;
2331
import org.springframework.core.convert.ConversionService;
2432
import org.springframework.core.convert.TypeDescriptor;
33+
import org.springframework.core.convert.converter.ConditionalConverter;
34+
import org.springframework.core.convert.converter.ConditionalGenericConverter;
2535
import org.springframework.core.convert.converter.Converter;
2636
import org.springframework.core.convert.converter.ConverterRegistry;
2737
import org.springframework.core.convert.converter.GenericConverter;
@@ -46,6 +56,7 @@
4656
* against registry instance.
4757
*
4858
* @author Phillip Webb
59+
* @author 郭 世雄
4960
* @since 2.0.0
5061
*/
5162
public class ApplicationConversionService extends FormattingConversionService {
@@ -188,17 +199,18 @@ public static void addApplicationFormatters(FormatterRegistry registry) {
188199
* @since 2.2.0
189200
*/
190201
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
191-
Set<Object> beans = new LinkedHashSet<>();
192-
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
193-
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
194-
beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
195-
beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
196-
for (Object bean : beans) {
202+
Set<Map.Entry<String, ?>> entries = new LinkedHashSet<>();
203+
entries.addAll(beanFactory.getBeansOfType(GenericConverter.class).entrySet());
204+
entries.addAll(beanFactory.getBeansOfType(Converter.class).entrySet());
205+
entries.addAll(beanFactory.getBeansOfType(Printer.class).entrySet());
206+
entries.addAll(beanFactory.getBeansOfType(Parser.class).entrySet());
207+
for (Map.Entry<String, ?> entity : entries) {
208+
Object bean = entity.getValue();
197209
if (bean instanceof GenericConverter) {
198210
registry.addConverter((GenericConverter) bean);
199211
}
200212
else if (bean instanceof Converter) {
201-
registry.addConverter((Converter<?, ?>) bean);
213+
addConverter(registry, beanFactory, entity);
202214
}
203215
else if (bean instanceof Formatter) {
204216
registry.addFormatter((Formatter<?>) bean);
@@ -212,4 +224,120 @@ else if (bean instanceof Parser) {
212224
}
213225
}
214226

227+
private static void addConverter(FormatterRegistry registry, ListableBeanFactory beanFactory,
228+
Map.Entry<String, ?> entity) {
229+
try {
230+
registry.addConverter((Converter<?, ?>) entity.getValue());
231+
}
232+
catch (IllegalArgumentException ex) {
233+
boolean success = tryAddMethodSignatureGenericsConverter(registry, beanFactory, entity);
234+
if (!success) {
235+
throw ex;
236+
}
237+
}
238+
}
239+
240+
private static boolean tryAddMethodSignatureGenericsConverter(FormatterRegistry registry,
241+
ListableBeanFactory beanFactory, Map.Entry<String, ?> entity) {
242+
ListableBeanFactory bf = beanFactory;
243+
if (bf instanceof ConfigurableApplicationContext) {
244+
bf = ((ConfigurableApplicationContext) bf).getBeanFactory();
245+
}
246+
if (bf instanceof ConfigurableListableBeanFactory) {
247+
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) bf;
248+
ConverterAdapter adapter = getConverterAdapter(clbf, entity);
249+
if (!Objects.isNull(adapter)) {
250+
registry.addConverter(adapter);
251+
return true;
252+
}
253+
}
254+
return false;
255+
}
256+
257+
private static ConverterAdapter getConverterAdapter(ConfigurableListableBeanFactory beanFactory,
258+
Map.Entry<String, ?> beanEntity) {
259+
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanEntity.getKey());
260+
ResolvableType resolvableType = beanDefinition.getResolvableType();
261+
ResolvableType[] types = resolvableType.getGenerics();
262+
if (types.length < 2) {
263+
return null;
264+
}
265+
return new ConverterAdapter((Converter<?, ?>) beanEntity.getValue(), types[0], types[1]);
266+
}
267+
268+
/**
269+
* Adapts a {@link Converter} to a {@link GenericConverter}.
270+
* <p>
271+
* Reference from
272+
* {@link org.springframework.core.convert.support.GenericConversionService.ConverterAdapter}
273+
*/
274+
@SuppressWarnings("unchecked")
275+
private static final class ConverterAdapter implements ConditionalGenericConverter {
276+
277+
private final Converter<Object, Object> converter;
278+
279+
private final ConvertiblePair typeInfo;
280+
281+
private final ResolvableType targetType;
282+
283+
ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) {
284+
this.converter = (Converter<Object, Object>) converter;
285+
this.typeInfo = new ConvertiblePair(sourceType.toClass(), targetType.toClass());
286+
this.targetType = targetType;
287+
}
288+
289+
@Override
290+
public Set<ConvertiblePair> getConvertibleTypes() {
291+
return Collections.singleton(this.typeInfo);
292+
}
293+
294+
@Override
295+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
296+
// Check raw type first...
297+
if (this.typeInfo.getTargetType() != targetType.getObjectType()) {
298+
return false;
299+
}
300+
// Full check for complex generic type match required?
301+
ResolvableType rt = targetType.getResolvableType();
302+
if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType)
303+
&& !this.targetType.hasUnresolvableGenerics()) {
304+
return false;
305+
}
306+
return !(this.converter instanceof ConditionalConverter)
307+
|| ((ConditionalConverter) this.converter).matches(sourceType, targetType);
308+
}
309+
310+
@Override
311+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
312+
if (source == null) {
313+
return convertNullSource(sourceType, targetType);
314+
}
315+
return this.converter.convert(source);
316+
}
317+
318+
@Override
319+
public String toString() {
320+
return (this.typeInfo + " : " + this.converter);
321+
}
322+
323+
/**
324+
* Template method to convert a {@code null} source.
325+
* <p>
326+
* The default implementation returns {@code null} or the Java 8
327+
* {@link java.util.Optional#empty()} instance if the target type is
328+
* {@code java.util.Optional}. Subclasses may override this to return custom
329+
* {@code null} objects for specific target types.
330+
* @param sourceType the source type to convert from
331+
* @param targetType the target type to convert to
332+
* @return the converted null object
333+
*/
334+
private Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) {
335+
if (targetType.getObjectType() == Optional.class) {
336+
return Optional.empty();
337+
}
338+
return null;
339+
}
340+
341+
}
342+
215343
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525

2626
import org.springframework.context.ConfigurableApplicationContext;
2727
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
28+
import org.springframework.context.annotation.Bean;
2829
import org.springframework.core.convert.TypeDescriptor;
30+
import org.springframework.core.convert.converter.ConditionalGenericConverter;
2931
import org.springframework.core.convert.converter.Converter;
3032
import org.springframework.core.convert.converter.GenericConverter;
3133
import org.springframework.format.Formatter;
@@ -34,6 +36,8 @@
3436
import org.springframework.format.Printer;
3537

3638
import static org.assertj.core.api.Assertions.assertThat;
39+
import static org.mockito.ArgumentMatchers.isA;
40+
import static org.mockito.BDDMockito.willThrow;
3741
import static org.mockito.Mockito.mock;
3842
import static org.mockito.Mockito.verify;
3943
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -42,6 +46,7 @@
4246
* Tests for {@link ApplicationConversionService}.
4347
*
4448
* @author Phillip Webb
49+
* @author 郭 世雄
4550
*/
4651
class ApplicationConversionServiceTests {
4752

@@ -66,6 +71,30 @@ void addBeansWhenHasConverterBeanAddConverter() {
6671
}
6772
}
6873

74+
@Test
75+
void addBeansWhenHasLambdaConverterBeanAddConverter() {
76+
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
77+
ExampleLambdaConverter.class)) {
78+
willThrow(IllegalArgumentException.class).given(this.registry).addConverter(isA(Converter.class));
79+
ApplicationConversionService.addBeans(this.registry, context);
80+
verify(this.registry).addConverter(context.getBean(Converter.class));
81+
verify(this.registry).addConverter(isA(ConditionalGenericConverter.class));
82+
verifyNoMoreInteractions(this.registry);
83+
}
84+
}
85+
86+
@Test
87+
void addBeansWhenHasMethodReferenceConverterBeanAddConverter() {
88+
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
89+
ExampleMethodReferenceConverter.class)) {
90+
willThrow(IllegalArgumentException.class).given(this.registry).addConverter(isA(Converter.class));
91+
ApplicationConversionService.addBeans(this.registry, context);
92+
verify(this.registry).addConverter(context.getBean(Converter.class));
93+
verify(this.registry).addConverter(isA(ConditionalGenericConverter.class));
94+
verifyNoMoreInteractions(this.registry);
95+
}
96+
}
97+
6998
@Test
7099
void addBeansWhenHasFormatterBeanAddsOnlyFormatter() {
71100
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExampleFormatter.class)) {
@@ -136,6 +165,24 @@ public Integer convert(String source) {
136165

137166
}
138167

168+
static class ExampleLambdaConverter {
169+
170+
@Bean
171+
Converter<String, Integer> converter() {
172+
return (e) -> Integer.valueOf(e);
173+
}
174+
175+
}
176+
177+
static class ExampleMethodReferenceConverter {
178+
179+
@Bean
180+
Converter<String, Integer> converter() {
181+
return Integer::valueOf;
182+
}
183+
184+
}
185+
139186
static class ExampleFormatter implements Formatter<Integer> {
140187

141188
@Override

0 commit comments

Comments
 (0)