提交 a27d1a28 编写于 作者: P Phillip Webb 提交者: Chris Beams

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
上级 f13e3ad7
/* /*
* Copyright 2002-2011 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -21,6 +21,7 @@ package org.springframework.core.convert; ...@@ -21,6 +21,7 @@ package org.springframework.core.convert;
* Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system. * Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
* *
* @author Keith Donald * @author Keith Donald
* @author Phillip Webb
* @since 3.0 * @since 3.0
*/ */
public interface ConversionService { public interface ConversionService {
...@@ -54,6 +55,28 @@ public interface ConversionService { ...@@ -54,6 +55,28 @@ public interface ConversionService {
*/ */
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* Returns true if conversion between the sourceType and targetType can be bypassed.
* More precisely this method will return true if objects of sourceType can be
* converted to the targetType by returning the source object unchanged.
* @param sourceType context about the source type to convert from (may be null if source is null)
* @param targetType context about the target type to convert to (required)
* @return true if conversion can be bypassed
* @throws IllegalArgumentException if targetType is null
*/
boolean canBypassConvert(Class<?> sourceType, Class<?> targetType);
/**
* Returns true if conversion between the sourceType and targetType can be bypassed.
* More precisely this method will return true if objects of sourceType can be
* converted to the targetType by returning the source object unchanged.
* @param sourceType context about the source type to convert from (may be null if source is null)
* @param targetType context about the target type to convert to (required)
* @return true if conversion can be bypassed
* @throws IllegalArgumentException if targetType is null
*/
boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
/** /**
* Convert the source to targetType. * Convert the source to targetType.
* @param source the source object to convert (may be null) * @param source the source object to convert (may be null)
......
/* /*
* Copyright 2002-2011 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,6 +18,7 @@ package org.springframework.core.convert.support; ...@@ -18,6 +18,7 @@ package org.springframework.core.convert.support;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
...@@ -26,18 +27,22 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter; ...@@ -26,18 +27,22 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
* Converts an Array to another Array. * Converts an Array to another Array. First adapts the source array to a List, then
* First adapts the source array to a List, then delegates to {@link CollectionToArrayConverter} to perform the target array conversion. * delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
* *
* @author Keith Donald * @author Keith Donald
* @author Phillip Webb
* @since 3.0 * @since 3.0
*/ */
final class ArrayToArrayConverter implements ConditionalGenericConverter { final class ArrayToArrayConverter implements ConditionalGenericConverter {
private final CollectionToArrayConverter helperConverter; private final CollectionToArrayConverter helperConverter;
private final ConversionService conversionService;
public ArrayToArrayConverter(ConversionService conversionService) { public ArrayToArrayConverter(ConversionService conversionService) {
this.helperConverter = new CollectionToArrayConverter(conversionService); this.helperConverter = new CollectionToArrayConverter(conversionService);
this.conversionService = conversionService;
} }
public Set<ConvertiblePair> getConvertibleTypes() { public Set<ConvertiblePair> getConvertibleTypes() {
...@@ -48,8 +53,14 @@ final class ArrayToArrayConverter implements ConditionalGenericConverter { ...@@ -48,8 +53,14 @@ final class ArrayToArrayConverter implements ConditionalGenericConverter {
return this.helperConverter.matches(sourceType, targetType); return this.helperConverter.matches(sourceType, targetType);
} }
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public Object convert(Object source, TypeDescriptor sourceType,
return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType); TypeDescriptor targetType) {
if (conversionService.canBypassConvert(sourceType.getElementTypeDescriptor(),
targetType.getElementTypeDescriptor())) {
return source;
}
List<Object> sourceList = Arrays.asList(ObjectUtils.toObjectArray(source));
return this.helperConverter.convert(sourceList, sourceType, targetType);
} }
} }
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -41,6 +41,9 @@ final class FallbackObjectToStringConverter implements ConditionalGenericConvert ...@@ -41,6 +41,9 @@ final class FallbackObjectToStringConverter implements ConditionalGenericConvert
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
Class<?> sourceClass = sourceType.getObjectType(); Class<?> sourceClass = sourceType.getObjectType();
if (String.class.equals(sourceClass)) {
return false;
}
return CharSequence.class.isAssignableFrom(sourceClass) || StringWriter.class.isAssignableFrom(sourceClass) || return CharSequence.class.isAssignableFrom(sourceClass) || StringWriter.class.isAssignableFrom(sourceClass) ||
ObjectToObjectConverter.hasValueOfMethodOrConstructor(sourceClass, String.class); ObjectToObjectConverter.hasValueOfMethodOrConstructor(sourceClass, String.class);
} }
......
...@@ -126,6 +126,21 @@ public class GenericConversionService implements ConfigurableConversionService { ...@@ -126,6 +126,21 @@ public class GenericConversionService implements ConfigurableConversionService {
return (converter != null); return (converter != null);
} }
public boolean canBypassConvert(Class<?> sourceType, Class<?> targetType) {
Assert.notNull(targetType, "The targetType to convert to cannot be null");
return canBypassConvert(sourceType != null ? TypeDescriptor.valueOf(sourceType)
: null, TypeDescriptor.valueOf(targetType));
}
public boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "The targetType to convert to cannot be null");
if (sourceType == null) {
return true;
}
GenericConverter converter = getConverter(sourceType, targetType);
return (converter == NO_OP_CONVERTER);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T convert(Object source, Class<T> targetType) { public <T> T convert(Object source, Class<T> targetType) {
Assert.notNull(targetType,"The targetType to convert to cannot be null"); Assert.notNull(targetType,"The targetType to convert to cannot be null");
......
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.core.convert.support; package org.springframework.core.convert.support;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalConversion;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.NumberUtils; import org.springframework.util.NumberUtils;
...@@ -38,12 +40,17 @@ import org.springframework.util.NumberUtils; ...@@ -38,12 +40,17 @@ import org.springframework.util.NumberUtils;
* @see java.math.BigDecimal * @see java.math.BigDecimal
* @see NumberUtils * @see NumberUtils
*/ */
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number> { final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>,
ConditionalConversion {
public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) { public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) {
return new NumberToNumber<T>(targetType); return new NumberToNumber<T>(targetType);
} }
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return !sourceType.equals(targetType);
}
private final static class NumberToNumber<T extends Number> implements Converter<Number, T> { private final static class NumberToNumber<T extends Number> implements Converter<Number, T> {
private final Class<T> targetType; private final Class<T> targetType;
......
...@@ -19,6 +19,7 @@ package org.springframework.core.convert.support; ...@@ -19,6 +19,7 @@ package org.springframework.core.convert.support;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
...@@ -709,6 +710,30 @@ public class GenericConversionServiceTests { ...@@ -709,6 +710,30 @@ public class GenericConversionServiceTests {
assertEquals(Object.class, last.getType()); assertEquals(Object.class, last.getType());
} }
@Test
public void convertOptimizeArray() throws Exception {
// SPR-9566
GenericConversionService conversionService = new DefaultConversionService();
byte[] byteArray = new byte[] { 1, 2, 3 };
byte[] converted = conversionService.convert(byteArray, byte[].class);
assertSame(byteArray, converted);
}
@Test
public void convertCannotOptimizeArray() throws Exception {
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(new Converter<Byte, Byte>() {
public Byte convert(Byte source) {
return (byte) (source + 1);
}
});
DefaultConversionService.addDefaultConverters(conversionService);
byte[] byteArray = new byte[] { 1, 2, 3 };
byte[] converted = conversionService.convert(byteArray, byte[].class);
assertNotSame(byteArray, converted);
assertTrue(Arrays.equals(new byte[] { 2, 3, 4 }, converted));
}
private static class MyConditionalConverter implements Converter<String, Color>, private static class MyConditionalConverter implements Converter<String, Color>,
ConditionalConversion { ConditionalConversion {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册