Skip to content

Commit a27d1a2

Browse files
philwebbcbeams
authored andcommitted
Bypass conversion when possible
Prior to this commit conversion between like types would often result in a copy of the object. This can be problematic in the case of large byte arrays and objects that do not have a default constructor. The ConversionService SPI now includes canBypassConvert methods that can be used to deduce when conversion is not needed. Several existing converters have been updated to ensure they only apply when source and target types differ. This change introduces new methods to the ConversionService that will break existing implementations. However, it anticipated that most users are consuming the ConversionService interface rather then extending it. Issue: SPR-9566
1 parent f13e3ad commit a27d1a2

File tree

6 files changed

+93
-9
lines changed

6 files changed

+93
-9
lines changed

spring-core/src/main/java/org/springframework/core/convert/ConversionService.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -21,6 +21,7 @@
2121
* Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
2222
*
2323
* @author Keith Donald
24+
* @author Phillip Webb
2425
* @since 3.0
2526
*/
2627
public interface ConversionService {
@@ -54,6 +55,28 @@ public interface ConversionService {
5455
*/
5556
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
5657

58+
/**
59+
* Returns true if conversion between the sourceType and targetType can be bypassed.
60+
* More precisely this method will return true if objects of sourceType can be
61+
* converted to the targetType by returning the source object unchanged.
62+
* @param sourceType context about the source type to convert from (may be null if source is null)
63+
* @param targetType context about the target type to convert to (required)
64+
* @return true if conversion can be bypassed
65+
* @throws IllegalArgumentException if targetType is null
66+
*/
67+
boolean canBypassConvert(Class<?> sourceType, Class<?> targetType);
68+
69+
/**
70+
* Returns true if conversion between the sourceType and targetType can be bypassed.
71+
* More precisely this method will return true if objects of sourceType can be
72+
* converted to the targetType by returning the source object unchanged.
73+
* @param sourceType context about the source type to convert from (may be null if source is null)
74+
* @param targetType context about the target type to convert to (required)
75+
* @return true if conversion can be bypassed
76+
* @throws IllegalArgumentException if targetType is null
77+
*/
78+
boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
79+
5780
/**
5881
* Convert the source to targetType.
5982
* @param source the source object to convert (may be null)

spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -18,6 +18,7 @@
1818

1919
import java.util.Arrays;
2020
import java.util.Collections;
21+
import java.util.List;
2122
import java.util.Set;
2223

2324
import org.springframework.core.convert.ConversionService;
@@ -26,18 +27,22 @@
2627
import org.springframework.util.ObjectUtils;
2728

2829
/**
29-
* Converts an Array to another Array.
30-
* First adapts the source array to a List, then delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
30+
* Converts an Array to another Array. First adapts the source array to a List, then
31+
* delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
3132
*
3233
* @author Keith Donald
34+
* @author Phillip Webb
3335
* @since 3.0
3436
*/
3537
final class ArrayToArrayConverter implements ConditionalGenericConverter {
3638

3739
private final CollectionToArrayConverter helperConverter;
3840

41+
private final ConversionService conversionService;
42+
3943
public ArrayToArrayConverter(ConversionService conversionService) {
4044
this.helperConverter = new CollectionToArrayConverter(conversionService);
45+
this.conversionService = conversionService;
4146
}
4247

4348
public Set<ConvertiblePair> getConvertibleTypes() {
@@ -48,8 +53,14 @@ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
4853
return this.helperConverter.matches(sourceType, targetType);
4954
}
5055

51-
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
52-
return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
56+
public Object convert(Object source, TypeDescriptor sourceType,
57+
TypeDescriptor targetType) {
58+
if (conversionService.canBypassConvert(sourceType.getElementTypeDescriptor(),
59+
targetType.getElementTypeDescriptor())) {
60+
return source;
61+
}
62+
List<Object> sourceList = Arrays.asList(ObjectUtils.toObjectArray(source));
63+
return this.helperConverter.convert(sourceList, sourceType, targetType);
5364
}
5465

5566
}

spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -41,6 +41,9 @@ public Set<ConvertiblePair> getConvertibleTypes() {
4141

4242
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
4343
Class<?> sourceClass = sourceType.getObjectType();
44+
if (String.class.equals(sourceClass)) {
45+
return false;
46+
}
4447
return CharSequence.class.isAssignableFrom(sourceClass) || StringWriter.class.isAssignableFrom(sourceClass) ||
4548
ObjectToObjectConverter.hasValueOfMethodOrConstructor(sourceClass, String.class);
4649
}

spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,21 @@ public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)
126126
return (converter != null);
127127
}
128128

129+
public boolean canBypassConvert(Class<?> sourceType, Class<?> targetType) {
130+
Assert.notNull(targetType, "The targetType to convert to cannot be null");
131+
return canBypassConvert(sourceType != null ? TypeDescriptor.valueOf(sourceType)
132+
: null, TypeDescriptor.valueOf(targetType));
133+
}
134+
135+
public boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
136+
Assert.notNull(targetType, "The targetType to convert to cannot be null");
137+
if (sourceType == null) {
138+
return true;
139+
}
140+
GenericConverter converter = getConverter(sourceType, targetType);
141+
return (converter == NO_OP_CONVERTER);
142+
}
143+
129144
@SuppressWarnings("unchecked")
130145
public <T> T convert(Object source, Class<T> targetType) {
131146
Assert.notNull(targetType,"The targetType to convert to cannot be null");

spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.core.convert.support;
1818

19+
import org.springframework.core.convert.TypeDescriptor;
20+
import org.springframework.core.convert.converter.ConditionalConversion;
1921
import org.springframework.core.convert.converter.Converter;
2022
import org.springframework.core.convert.converter.ConverterFactory;
2123
import org.springframework.util.NumberUtils;
@@ -38,12 +40,17 @@
3840
* @see java.math.BigDecimal
3941
* @see NumberUtils
4042
*/
41-
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number> {
43+
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>,
44+
ConditionalConversion {
4245

4346
public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) {
4447
return new NumberToNumber<T>(targetType);
4548
}
4649

50+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
51+
return !sourceType.equals(targetType);
52+
}
53+
4754
private final static class NumberToNumber<T extends Number> implements Converter<Number, T> {
4855

4956
private final Class<T> targetType;

spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.junit.Assert.assertEquals;
2020
import static org.junit.Assert.assertFalse;
2121
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertNotSame;
2223
import static org.junit.Assert.assertNull;
2324
import static org.junit.Assert.assertSame;
2425
import static org.junit.Assert.assertTrue;
@@ -709,6 +710,30 @@ public void conditionalConversionForAllTypes() throws Exception {
709710
assertEquals(Object.class, last.getType());
710711
}
711712

713+
@Test
714+
public void convertOptimizeArray() throws Exception {
715+
// SPR-9566
716+
GenericConversionService conversionService = new DefaultConversionService();
717+
byte[] byteArray = new byte[] { 1, 2, 3 };
718+
byte[] converted = conversionService.convert(byteArray, byte[].class);
719+
assertSame(byteArray, converted);
720+
}
721+
722+
@Test
723+
public void convertCannotOptimizeArray() throws Exception {
724+
GenericConversionService conversionService = new GenericConversionService();
725+
conversionService.addConverter(new Converter<Byte, Byte>() {
726+
public Byte convert(Byte source) {
727+
return (byte) (source + 1);
728+
}
729+
});
730+
DefaultConversionService.addDefaultConverters(conversionService);
731+
byte[] byteArray = new byte[] { 1, 2, 3 };
732+
byte[] converted = conversionService.convert(byteArray, byte[].class);
733+
assertNotSame(byteArray, converted);
734+
assertTrue(Arrays.equals(new byte[] { 2, 3, 4 }, converted));
735+
}
736+
712737
private static class MyConditionalConverter implements Converter<String, Color>,
713738
ConditionalConversion {
714739

0 commit comments

Comments
 (0)