Skip to content

Commit f7d740f

Browse files
committed
ConversionService detects generic type declaration on target class behind proxy as well
Issue: SPR-14822
1 parent 52b029d commit f7d740f

File tree

5 files changed

+129
-41
lines changed

5 files changed

+129
-41
lines changed

spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.springframework.context.EmbeddedValueResolverAware;
2626
import org.springframework.context.i18n.LocaleContextHolder;
27+
import org.springframework.core.DecoratingProxy;
2728
import org.springframework.core.GenericTypeResolver;
2829
import org.springframework.core.convert.ConversionService;
2930
import org.springframework.core.convert.TypeDescriptor;
@@ -97,9 +98,13 @@ public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends
9798

9899
static Class<?> getFieldType(Formatter<?> formatter) {
99100
Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
101+
if (fieldType == null && formatter instanceof DecoratingProxy) {
102+
fieldType = GenericTypeResolver.resolveTypeArgument(
103+
((DecoratingProxy) formatter).getDecoratedClass(), Formatter.class);
104+
}
100105
if (fieldType == null) {
101-
throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" +
102-
formatter.getClass().getName() + "]; does the formatter parameterize the <T> generic type?");
106+
throw new IllegalArgumentException("Unable to extract the parameterized field type from Formatter [" +
107+
formatter.getClass().getName() + "]; does the class parameterize the <T> generic type?");
103108
}
104109
return fieldType;
105110
}

spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import org.junit.Before;
3333
import org.junit.Test;
3434

35-
import org.springframework.beans.BeanUtils;
35+
import org.springframework.aop.framework.ProxyFactory;
3636
import org.springframework.beans.ConfigurablePropertyAccessor;
3737
import org.springframework.beans.PropertyAccessorFactory;
3838
import org.springframework.beans.factory.annotation.Value;
@@ -45,6 +45,7 @@
4545
import org.springframework.core.convert.ConversionFailedException;
4646
import org.springframework.core.convert.TypeDescriptor;
4747
import org.springframework.core.convert.converter.Converter;
48+
import org.springframework.core.convert.converter.ConverterFactory;
4849
import org.springframework.core.convert.support.DefaultConversionService;
4950
import org.springframework.format.Formatter;
5051
import org.springframework.format.Printer;
@@ -81,7 +82,7 @@ public void tearDown() {
8182

8283

8384
@Test
84-
public void testFormatFieldForTypeWithFormatter() throws ParseException {
85+
public void formatFieldForTypeWithFormatter() throws ParseException {
8586
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
8687
String formatted = formattingService.convert(3, String.class);
8788
assertEquals("3", formatted);
@@ -90,7 +91,7 @@ public void testFormatFieldForTypeWithFormatter() throws ParseException {
9091
}
9192

9293
@Test
93-
public void testFormatFieldForTypeWithPrinterParserWithCoercion() throws ParseException {
94+
public void formatFieldForTypeWithPrinterParserWithCoercion() throws ParseException {
9495
formattingService.addConverter(new Converter<DateTime, LocalDate>() {
9596
@Override
9697
public LocalDate convert(DateTime source) {
@@ -107,7 +108,7 @@ public LocalDate convert(DateTime source) {
107108

108109
@Test
109110
@SuppressWarnings("resource")
110-
public void testFormatFieldForValueInjection() {
111+
public void formatFieldForValueInjection() {
111112
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
112113
ac.registerBeanDefinition("valueBean", new RootBeanDefinition(ValueBean.class));
113114
ac.registerBeanDefinition("conversionService", new RootBeanDefinition(FormattingConversionServiceFactoryBean.class));
@@ -118,7 +119,7 @@ public void testFormatFieldForValueInjection() {
118119

119120
@Test
120121
@SuppressWarnings("resource")
121-
public void testFormatFieldForValueInjectionUsingMetaAnnotations() {
122+
public void formatFieldForValueInjectionUsingMetaAnnotations() {
122123
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
123124
RootBeanDefinition bd = new RootBeanDefinition(MetaValueBean.class);
124125
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
@@ -140,20 +141,20 @@ public void testFormatFieldForValueInjectionUsingMetaAnnotations() {
140141
}
141142

142143
@Test
143-
public void testFormatFieldForAnnotation() throws Exception {
144+
public void formatFieldForAnnotation() throws Exception {
144145
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
145146
doTestFormatFieldForAnnotation(Model.class, false);
146147
}
147148

148149
@Test
149-
public void testFormatFieldForAnnotationWithDirectFieldAccess() throws Exception {
150+
public void formatFieldForAnnotationWithDirectFieldAccess() throws Exception {
150151
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
151152
doTestFormatFieldForAnnotation(Model.class, true);
152153
}
153154

154155
@Test
155156
@SuppressWarnings("resource")
156-
public void testFormatFieldForAnnotationWithPlaceholders() throws Exception {
157+
public void formatFieldForAnnotationWithPlaceholders() throws Exception {
157158
GenericApplicationContext context = new GenericApplicationContext();
158159
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
159160
Properties props = new Properties();
@@ -169,7 +170,7 @@ public void testFormatFieldForAnnotationWithPlaceholders() throws Exception {
169170

170171
@Test
171172
@SuppressWarnings("resource")
172-
public void testFormatFieldForAnnotationWithPlaceholdersAndFactoryBean() throws Exception {
173+
public void formatFieldForAnnotationWithPlaceholdersAndFactoryBean() throws Exception {
173174
GenericApplicationContext context = new GenericApplicationContext();
174175
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
175176
Properties props = new Properties();
@@ -239,61 +240,61 @@ public Date convert(DateTime source) {
239240
}
240241

241242
@Test
242-
public void testPrintNull() throws ParseException {
243+
public void printNull() throws ParseException {
243244
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
244245
assertEquals("", formattingService.convert(null, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(String.class)));
245246
}
246247

247248
@Test
248-
public void testParseNull() throws ParseException {
249+
public void parseNull() throws ParseException {
249250
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
250251
assertNull(formattingService
251252
.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
252253
}
253254

254255
@Test
255-
public void testParseEmptyString() throws ParseException {
256+
public void parseEmptyString() throws ParseException {
256257
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
257258
assertNull(formattingService.convert("", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
258259
}
259260

260261
@Test
261-
public void testParseBlankString() throws ParseException {
262+
public void parseBlankString() throws ParseException {
262263
formattingService.addFormatterForFieldType(Number.class, new NumberStyleFormatter());
263264
assertNull(formattingService.convert(" ", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
264265
}
265266

266267
@Test(expected = ConversionFailedException.class)
267-
public void testParseParserReturnsNull() throws ParseException {
268+
public void parseParserReturnsNull() throws ParseException {
268269
formattingService.addFormatterForFieldType(Integer.class, new NullReturningFormatter());
269270
assertNull(formattingService.convert("1", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
270271
}
271272

272273
@Test(expected = ConversionFailedException.class)
273-
public void testParseNullPrimitiveProperty() throws ParseException {
274+
public void parseNullPrimitiveProperty() throws ParseException {
274275
formattingService.addFormatterForFieldType(Integer.class, new NumberStyleFormatter());
275276
assertNull(formattingService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(int.class)));
276277
}
277278

278279
@Test
279-
public void testPrintNullDefault() throws ParseException {
280+
public void printNullDefault() throws ParseException {
280281
assertEquals(null, formattingService
281282
.convert(null, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(String.class)));
282283
}
283284

284285
@Test
285-
public void testParseNullDefault() throws ParseException {
286+
public void parseNullDefault() throws ParseException {
286287
assertNull(formattingService
287288
.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
288289
}
289290

290291
@Test
291-
public void testParseEmptyStringDefault() throws ParseException {
292+
public void parseEmptyStringDefault() throws ParseException {
292293
assertNull(formattingService.convert("", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
293294
}
294295

295296
@Test
296-
public void testFormatFieldForAnnotationWithSubclassAsFieldType() throws Exception {
297+
public void formatFieldForAnnotationWithSubclassAsFieldType() throws Exception {
297298
formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory() {
298299
@Override
299300
public Printer<?> getPrinter(org.springframework.format.annotation.DateTimeFormat annotation, Class<?> fieldType) {
@@ -319,7 +320,7 @@ public Date convert(MyDate source) {
319320
}
320321

321322
@Test
322-
public void testRegisterDefaultValueViaFormatter() {
323+
public void registerDefaultValueViaFormatter() {
323324
registerDefaultValue(Date.class, new Date());
324325
}
325326

@@ -340,6 +341,45 @@ public String toString() {
340341
});
341342
}
342343

344+
@Test
345+
public void introspectedFormatter() throws ParseException {
346+
formattingService.addFormatter(new NumberStyleFormatter());
347+
assertNull(formattingService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
348+
}
349+
350+
@Test
351+
public void proxiedFormatter() throws ParseException {
352+
Formatter<?> formatter = new NumberStyleFormatter();
353+
formattingService.addFormatter((Formatter<?>) new ProxyFactory(formatter).getProxy());
354+
assertNull(formattingService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
355+
}
356+
357+
@Test
358+
public void introspectedConverter() {
359+
formattingService.addConverter(new IntegerConverter());
360+
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
361+
}
362+
363+
@Test
364+
public void proxiedConverter() {
365+
Converter<?, ?> converter = new IntegerConverter();
366+
formattingService.addConverter((Converter<?, ?>) new ProxyFactory(converter).getProxy());
367+
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
368+
}
369+
370+
@Test
371+
public void introspectedConverterFactory() {
372+
formattingService.addConverterFactory(new IntegerConverterFactory());
373+
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
374+
}
375+
376+
@Test
377+
public void proxiedConverterFactory() {
378+
ConverterFactory<?, ?> converterFactory = new IntegerConverterFactory();
379+
formattingService.addConverterFactory((ConverterFactory<?, ?>) new ProxyFactory(converterFactory).getProxy());
380+
assertEquals(Integer.valueOf(1), formattingService.convert("1", Integer.class));
381+
}
382+
343383

344384
public static class ValueBean {
345385

@@ -348,6 +388,7 @@ public static class ValueBean {
348388
public Date date;
349389
}
350390

391+
351392
public static class MetaValueBean {
352393

353394
@MyDateAnn
@@ -357,18 +398,21 @@ public static class MetaValueBean {
357398
public Double number;
358399
}
359400

401+
360402
@Value("${myDate}")
361403
@org.springframework.format.annotation.DateTimeFormat(pattern="MM-d-yy")
362404
@Retention(RetentionPolicy.RUNTIME)
363-
public static @interface MyDateAnn {
405+
public @interface MyDateAnn {
364406
}
365407

408+
366409
@Value("${myNumber}")
367410
@NumberFormat(style = NumberFormat.Style.PERCENT)
368411
@Retention(RetentionPolicy.RUNTIME)
369-
public static @interface MyNumberAnn {
412+
public @interface MyNumberAnn {
370413
}
371414

415+
372416
public static class Model {
373417

374418
@org.springframework.format.annotation.DateTimeFormat(style="S-")
@@ -386,6 +430,7 @@ public void setDates(List<Date> dates) {
386430
}
387431
}
388432

433+
389434
public static class ModelWithPlaceholders {
390435

391436
@org.springframework.format.annotation.DateTimeFormat(style="${dateStyle}")
@@ -403,11 +448,13 @@ public void setDates(List<Date> dates) {
403448
}
404449
}
405450

451+
406452
@org.springframework.format.annotation.DateTimeFormat(pattern="${datePattern}")
407453
@Retention(RetentionPolicy.RUNTIME)
408-
public static @interface MyDatePattern {
454+
public @interface MyDatePattern {
409455
}
410456

457+
411458
public static class NullReturningFormatter implements Formatter<Integer> {
412459

413460
@Override
@@ -419,17 +466,42 @@ public String print(Integer object, Locale locale) {
419466
public Integer parse(String text, Locale locale) throws ParseException {
420467
return null;
421468
}
422-
423469
}
424470

471+
425472
@SuppressWarnings("serial")
426473
public static class MyDate extends Date {
427474
}
428475

476+
429477
private static class ModelWithSubclassField {
430478

431479
@org.springframework.format.annotation.DateTimeFormat(style = "S-")
432480
public MyDate date;
433481
}
434482

483+
484+
private static class IntegerConverter implements Converter<String, Integer> {
485+
486+
@Override
487+
public Integer convert(String source) {
488+
return Integer.parseInt(source);
489+
}
490+
}
491+
492+
493+
private static class IntegerConverterFactory implements ConverterFactory<String, Number> {
494+
495+
@Override
496+
@SuppressWarnings("unchecked")
497+
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
498+
if (Integer.class == targetType) {
499+
return (Converter<String, T>) new IntegerConverter();
500+
}
501+
else {
502+
throw new IllegalStateException();
503+
}
504+
}
505+
}
506+
435507
}

spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -34,7 +34,7 @@ public interface ConverterFactory<S, R> {
3434
* Get the converter to convert from S to target type T, where T is also an instance of R.
3535
* @param <T> the target type
3636
* @param targetType the target type to convert to
37-
* @return A converter from S to T
37+
* @return a converter from S to T
3838
*/
3939
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
4040

spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ public interface ConverterRegistry {
4949
/**
5050
* Add a ranged converter factory to this registry.
5151
* The convertible source/target type pair is derived from the ConverterFactory's parameterized types.
52-
* @throws IllegalArgumentException if the parameterized types could not be resolved.
52+
* @throws IllegalArgumentException if the parameterized types could not be resolved
5353
*/
54-
void addConverterFactory(ConverterFactory<?, ?> converterFactory);
54+
void addConverterFactory(ConverterFactory<?, ?> factory);
5555

5656
/**
57-
* Remove any converters from sourceType to targetType.
57+
* Remove any converters from {@code sourceType} to {@code targetType}.
5858
* @param sourceType the source type
5959
* @param targetType the target type
6060
*/

0 commit comments

Comments
 (0)