Skip to content

Commit 2e6af25

Browse files
committedAug 11, 2020
Converter supports lambda and method references
1 parent 4933b26 commit 2e6af25

File tree

2 files changed

+156
-7
lines changed

2 files changed

+156
-7
lines changed
 

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

+114-7
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@
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.core.ResolvableType;
2330
import org.springframework.core.convert.ConversionService;
31+
import org.springframework.core.convert.TypeDescriptor;
32+
import org.springframework.core.convert.converter.ConditionalConverter;
33+
import org.springframework.core.convert.converter.ConditionalGenericConverter;
2434
import org.springframework.core.convert.converter.Converter;
2535
import org.springframework.core.convert.converter.ConverterRegistry;
2636
import org.springframework.core.convert.converter.GenericConverter;
@@ -44,6 +54,7 @@
4454
* against registry instance.
4555
*
4656
* @author Phillip Webb
57+
* @author 郭世雄 Viviel
4758
* @since 2.0.0
4859
*/
4960
public class ApplicationConversionService extends FormattingConversionService {
@@ -157,17 +168,30 @@ public static void addApplicationFormatters(FormatterRegistry registry) {
157168
* @since 2.2.0
158169
*/
159170
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
160-
Set<Object> beans = new LinkedHashSet<>();
161-
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
162-
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
163-
beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
164-
beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
165-
for (Object bean : beans) {
171+
Set<Map.Entry<String, ?>> entries = new LinkedHashSet<>();
172+
entries.addAll(beanFactory.getBeansOfType(GenericConverter.class).entrySet());
173+
entries.addAll(beanFactory.getBeansOfType(Converter.class).entrySet());
174+
entries.addAll(beanFactory.getBeansOfType(Printer.class).entrySet());
175+
entries.addAll(beanFactory.getBeansOfType(Parser.class).entrySet());
176+
for (Map.Entry<String, ?> entity : entries) {
177+
Object bean = entity.getValue();
166178
if (bean instanceof GenericConverter) {
167179
registry.addConverter((GenericConverter) bean);
168180
}
169181
else if (bean instanceof Converter) {
170-
registry.addConverter((Converter<?, ?>) bean);
182+
if (beanFactory instanceof ConfigurableListableBeanFactory) {
183+
ConverterAdapter adapter = getConverterAdapter((ConfigurableListableBeanFactory) beanFactory,
184+
entity);
185+
if (!Objects.isNull(adapter)) {
186+
registry.addConverter(adapter);
187+
}
188+
else {
189+
registry.addConverter((Converter<?, ?>) bean);
190+
}
191+
}
192+
else {
193+
registry.addConverter((Converter<?, ?>) bean);
194+
}
171195
}
172196
else if (bean instanceof Formatter) {
173197
registry.addFormatter((Formatter<?>) bean);
@@ -181,4 +205,87 @@ else if (bean instanceof Parser) {
181205
}
182206
}
183207

208+
private static ConverterAdapter getConverterAdapter(ConfigurableListableBeanFactory beanFactory,
209+
Map.Entry<String, ?> beanEntity) {
210+
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanEntity.getKey());
211+
ResolvableType resolvableType = beanDefinition.getResolvableType();
212+
ResolvableType[] types = resolvableType.getGenerics();
213+
if (types.length < 2) {
214+
return null;
215+
}
216+
return new ConverterAdapter((Converter<?, ?>) beanEntity.getValue(), types[0], types[1]);
217+
}
218+
219+
/**
220+
* Adapts a {@link Converter} to a {@link GenericConverter}.
221+
*/
222+
@SuppressWarnings("unchecked")
223+
private static final class ConverterAdapter implements ConditionalGenericConverter {
224+
225+
private final Converter<Object, Object> converter;
226+
227+
private final ConvertiblePair typeInfo;
228+
229+
private final ResolvableType targetType;
230+
231+
ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) {
232+
this.converter = (Converter<Object, Object>) converter;
233+
this.typeInfo = new ConvertiblePair(sourceType.toClass(), targetType.toClass());
234+
this.targetType = targetType;
235+
}
236+
237+
@Override
238+
public Set<ConvertiblePair> getConvertibleTypes() {
239+
return Collections.singleton(this.typeInfo);
240+
}
241+
242+
@Override
243+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
244+
// Check raw type first...
245+
if (this.typeInfo.getTargetType() != targetType.getObjectType()) {
246+
return false;
247+
}
248+
// Full check for complex generic type match required?
249+
ResolvableType rt = targetType.getResolvableType();
250+
if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType)
251+
&& !this.targetType.hasUnresolvableGenerics()) {
252+
return false;
253+
}
254+
return !(this.converter instanceof ConditionalConverter)
255+
|| ((ConditionalConverter) this.converter).matches(sourceType, targetType);
256+
}
257+
258+
@Override
259+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
260+
if (source == null) {
261+
return convertNullSource(sourceType, targetType);
262+
}
263+
return this.converter.convert(source);
264+
}
265+
266+
@Override
267+
public String toString() {
268+
return (this.typeInfo + " : " + this.converter);
269+
}
270+
271+
/**
272+
* Template method to convert a {@code null} source.
273+
* <p>
274+
* The default implementation returns {@code null} or the Java 8
275+
* {@link java.util.Optional#empty()} instance if the target type is
276+
* {@code java.util.Optional}. Subclasses may override this to return custom
277+
* {@code null} objects for specific target types.
278+
* @param sourceType the source type to convert from
279+
* @param targetType the target type to convert to
280+
* @return the converted null object
281+
*/
282+
private Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) {
283+
if (targetType.getObjectType() == Optional.class) {
284+
return Optional.empty();
285+
}
286+
return null;
287+
}
288+
289+
}
290+
184291
}

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

+42
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import org.springframework.context.ConfigurableApplicationContext;
2626
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
2729
import org.springframework.core.convert.TypeDescriptor;
2830
import org.springframework.core.convert.converter.Converter;
2931
import org.springframework.core.convert.converter.GenericConverter;
@@ -64,6 +66,26 @@ void addBeansWhenHasConverterBeanAddConverter() {
6466
}
6567
}
6668

69+
@Test
70+
void addBeansWhenHasLambdaConverterBeanAddConverter() {
71+
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
72+
ExampleLambdaConverter.class)) {
73+
ApplicationConversionService.addBeans(this.registry, context);
74+
verify(this.registry).addConverter(context.getBean(Converter.class));
75+
verifyNoMoreInteractions(this.registry);
76+
}
77+
}
78+
79+
@Test
80+
void addBeansWhenHasMethodReferenceConverterBeanAddConverter() {
81+
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
82+
ExampleMethodReferenceConverter.class)) {
83+
ApplicationConversionService.addBeans(this.registry, context);
84+
verify(this.registry).addConverter(context.getBean(Converter.class));
85+
verifyNoMoreInteractions(this.registry);
86+
}
87+
}
88+
6789
@Test
6890
void addBeansWhenHasFormatterBeanAddsOnlyFormatter() {
6991
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExampleFormatter.class)) {
@@ -114,6 +136,26 @@ public Integer convert(String source) {
114136

115137
}
116138

139+
@Configuration
140+
static class ExampleLambdaConverter {
141+
142+
@Bean
143+
public Converter<String, Integer> converter() {
144+
return e -> Integer.valueOf(e);
145+
}
146+
147+
}
148+
149+
@Configuration
150+
static class ExampleMethodReferenceConverter {
151+
152+
@Bean
153+
public Converter<String, Integer> converter() {
154+
return Integer::valueOf;
155+
}
156+
157+
}
158+
117159
static class ExampleFormatter implements Formatter<Integer> {
118160

119161
@Override

0 commit comments

Comments
 (0)