Skip to content

Commit f4e05c9

Browse files
committed
Use converter beans in preference to ObjectToObjectConverter
Previously, with the converter beans in a conversion service that appears after the bean factory's conversion service, they would not be called for a conversion that could be handled by the ObjectToObjectConverter in the bean factory's conversion service. This commit creates a new FormattingConversionService that is empty except for the converter beans and places it first in the list. It's followed by the bean factory's conversion service. The shared application conversion service is added to the end of the list to pick up any conversions that the previous two services could not handle. This should maintain backwards compatibility with the previous arrangement where the converter beans were added to an application conversion service that went after the bean factory's conversion service. Fixes gh-34631
1 parent 6041fe9 commit f4e05c9

File tree

3 files changed

+54
-29
lines changed

3 files changed

+54
-29
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.convert.converter.GenericConverter;
3232
import org.springframework.format.Formatter;
3333
import org.springframework.format.FormatterRegistry;
34+
import org.springframework.format.support.FormattingConversionService;
3435

3536
/**
3637
* Utility to deduce the {@link ConversionService} to use for configuration properties
@@ -59,15 +60,22 @@ List<ConversionService> getConversionServices() {
5960

6061
private List<ConversionService> getConversionServices(ConfigurableApplicationContext applicationContext) {
6162
List<ConversionService> conversionServices = new ArrayList<>();
62-
if (applicationContext.getBeanFactory().getConversionService() != null) {
63-
conversionServices.add(applicationContext.getBeanFactory().getConversionService());
64-
}
6563
ConverterBeans converterBeans = new ConverterBeans(applicationContext);
6664
if (!converterBeans.isEmpty()) {
67-
ApplicationConversionService beansConverterService = new ApplicationConversionService();
65+
FormattingConversionService beansConverterService = new FormattingConversionService();
6866
converterBeans.addTo(beansConverterService);
6967
conversionServices.add(beansConverterService);
7068
}
69+
if (applicationContext.getBeanFactory().getConversionService() != null) {
70+
conversionServices.add(applicationContext.getBeanFactory().getConversionService());
71+
}
72+
if (!converterBeans.isEmpty()) {
73+
// Converters beans used to be added to a custom ApplicationConversionService
74+
// after the BeanFactory's ConversionService. For backwards compatibility, we
75+
// add an ApplicationConversationService as a fallback in the same place in
76+
// the list.
77+
conversionServices.add(ApplicationConversionService.getSharedInstance());
78+
}
7179
return conversionServices;
7280
}
7381

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -644,24 +644,36 @@ void customProtocolResolver() {
644644

645645
@Test
646646
void loadShouldUseConverterBean() {
647-
prepareConverterContext(ConverterConfiguration.class, PersonProperties.class);
647+
prepareConverterContext(PersonConverterConfiguration.class, PersonProperties.class);
648648
Person person = this.context.getBean(PersonProperties.class).getPerson();
649649
assertThat(person.firstName).isEqualTo("John");
650650
assertThat(person.lastName).isEqualTo("Smith");
651651
}
652652

653653
@Test
654-
void loadWhenBeanFactoryConversionServiceAndConverterBean() {
654+
void loadWhenBeanFactoryConversionServiceAndConverterBeanCanUseBeanFactoryConverter() {
655655
DefaultConversionService conversionService = new DefaultConversionService();
656656
conversionService.addConverter(new AlienConverter());
657657
this.context.getBeanFactory().setConversionService(conversionService);
658-
load(new Class<?>[] { ConverterConfiguration.class, PersonAndAlienProperties.class }, "test.person=John Smith",
659-
"test.alien=Alf Tanner");
658+
load(new Class<?>[] { PersonConverterConfiguration.class, PersonAndAlienProperties.class },
659+
"test.person=John Smith", "test.alien=Alf Tanner");
660660
PersonAndAlienProperties properties = this.context.getBean(PersonAndAlienProperties.class);
661661
assertThat(properties.getPerson().firstName).isEqualTo("John");
662662
assertThat(properties.getPerson().lastName).isEqualTo("Smith");
663-
assertThat(properties.getAlien().firstName).isEqualTo("Alf");
664-
assertThat(properties.getAlien().lastName).isEqualTo("Tanner");
663+
assertThat(properties.getAlien().name).isEqualTo("rennaT flA");
664+
}
665+
666+
@Test
667+
void loadWhenBeanFactoryConversionServiceAndConverterBeanCanUseConverterBean() {
668+
DefaultConversionService conversionService = new DefaultConversionService();
669+
conversionService.addConverter(new PersonConverter());
670+
this.context.getBeanFactory().setConversionService(conversionService);
671+
load(new Class<?>[] { AlienConverterConfiguration.class, PersonAndAlienProperties.class },
672+
"test.person=John Smith", "test.alien=Alf Tanner");
673+
PersonAndAlienProperties properties = this.context.getBean(PersonAndAlienProperties.class);
674+
assertThat(properties.getPerson().firstName).isEqualTo("John");
675+
assertThat(properties.getPerson().lastName).isEqualTo("Smith");
676+
assertThat(properties.getAlien().name).isEqualTo("rennaT flA");
665677
}
666678

667679
@Test
@@ -1440,7 +1452,7 @@ public Resource resolve(String location, ResourceLoader resourceLoader) {
14401452
}
14411453

14421454
@Configuration(proxyBeanMethods = false)
1443-
static class ConverterConfiguration {
1455+
static class PersonConverterConfiguration {
14441456

14451457
@Bean
14461458
@ConfigurationPropertiesBinding
@@ -1450,6 +1462,17 @@ Converter<String, Person> personConverter() {
14501462

14511463
}
14521464

1465+
@Configuration(proxyBeanMethods = false)
1466+
static class AlienConverterConfiguration {
1467+
1468+
@Bean
1469+
@ConfigurationPropertiesBinding
1470+
Converter<String, Alien> alienConverter() {
1471+
return new AlienConverter();
1472+
}
1473+
1474+
}
1475+
14531476
@Configuration(proxyBeanMethods = false)
14541477
static class NonQualifiedConverterConfiguration {
14551478

@@ -2398,8 +2421,7 @@ static class AlienConverter implements Converter<String, Alien> {
23982421

23992422
@Override
24002423
public Alien convert(String source) {
2401-
String[] content = StringUtils.split(source, " ");
2402-
return new Alien(content[0], content[1]);
2424+
return new Alien(new StringBuilder(source).reverse().toString());
24032425
}
24042426

24052427
}
@@ -2467,21 +2489,14 @@ String getLastName() {
24672489

24682490
static class Alien {
24692491

2470-
private final String firstName;
2471-
2472-
private final String lastName;
2473-
2474-
Alien(String firstName, String lastName) {
2475-
this.firstName = firstName;
2476-
this.lastName = lastName;
2477-
}
2492+
private final String name;
24782493

2479-
String getFirstName() {
2480-
return this.firstName;
2494+
Alien(String name) {
2495+
this.name = name;
24812496
}
24822497

2483-
String getLastName() {
2484-
return this.lastName;
2498+
String getName() {
2499+
return this.name;
24852500
}
24862501

24872502
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConversionServiceDeducerTests.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2023 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.
@@ -30,6 +30,7 @@
3030
import org.springframework.context.annotation.Configuration;
3131
import org.springframework.core.convert.ConversionService;
3232
import org.springframework.core.convert.converter.Converter;
33+
import org.springframework.format.support.FormattingConversionService;
3334

3435
import static org.assertj.core.api.Assertions.assertThat;
3536

@@ -69,14 +70,15 @@ void getConversionServiceWhenHasNoConversionServiceBeanAndNoQualifiedBeansAndBea
6970
}
7071

7172
@Test
72-
void getConversionServiceWhenHasQualifiedConverterBeansContainsCustomizedApplicationService() {
73+
void getConversionServiceWhenHasQualifiedConverterBeansContainsCustomizedFormattingService() {
7374
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
7475
CustomConverterConfiguration.class);
7576
ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext);
7677
List<ConversionService> conversionServices = deducer.getConversionServices();
77-
assertThat(conversionServices).hasSize(1);
78-
assertThat(conversionServices.get(0)).isNotSameAs(ApplicationConversionService.getSharedInstance());
78+
assertThat(conversionServices).hasSize(2);
79+
assertThat(conversionServices.get(0)).isExactlyInstanceOf(FormattingConversionService.class);
7980
assertThat(conversionServices.get(0).canConvert(InputStream.class, OutputStream.class)).isTrue();
81+
assertThat(conversionServices.get(1)).isSameAs(ApplicationConversionService.getSharedInstance());
8082
}
8183

8284
@Configuration(proxyBeanMethods = false)

0 commit comments

Comments
 (0)