Type conversion is essentially a process:
SourceType --> TargetType
Spring provides a set of SPI(Server Provide Interface) mechanism based on Converter interface.
By implementing the Converter interface, we can formulate specific type conversion rules according to our business needs.
1 Converter interface
org.springframework.core.convert.converter.Converter:
public interface Converter<S, T> { @Nullable T convert(S source); default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) { Assert.notNull(after, "After Converter must not be null"); return (S s) -> { T initialResult = convert(s); return (initialResult != null ? after.convert(initialResult) : null); }; } }
Generic interpretation:
- S: Original type.
- T: Target type.
Method interpretation:
- convert(): converts data from S type to T type.
- andThen(): chained type conversion, S → T → U.
For example, if we want to implement an Object → String type conversion, we can follow the following steps:
- Implement the Converter interface, and specify that generic S is Object and generic T is String;
- Implement the convert() method and write conversion rules.
public class ObjectToStringConverter implements Converter<Object, String> { @Override public String convert(Object source) { return source.toString(); } }
In Spring, you can call it in the following ways:
- Create ObjectToStringConverter, which is managed by the container:
@Configuration public class AppConfig { @Bean public Converter converter() { return new ObjectToStringConverter(); } }
- Inject the Converter instance into the business:
@Service public class AppService { @Resource private Converter converter; public void service() { Date source = new Date(); Object target = converter.convert(source); System.out.println(target.getClass()); System.out.println(target); } }
- After the service() method is called by the outside world, the following contents will be output, indicating that Date is successfully converted to String:
class java.lang.String Thu Jan 13 21:33:49 CST 2022
Spring's type conversion mechanism is essentially such a routine. By implementing the Converter interface, you can implement different type conversion rules, which are applicable to different business scenarios.
Of course, Spring also implements many commonly used xxconverters. When you need type conversion, you can first see whether the inheritance level of the Converter can be used directly.
2 ConverterFactory interface
In order to manage a series of converters with hierarchical relationships, Spring also provides the ConverterFactory interface.
org.springframework.core.convert.converter.ConverterFactory:
public interface ConverterFactory<S, R> { <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
Generic interpretation:
- S: Original type.
- R: The top-level parent / interface of the target type.
- T: The specific target type is a subclass / implementation class of R.
Method interpretation:
- getConverter(): get the Converter implementation class with target type T.
For example, if there are the following business requirements:
- Charater → Integer
- Charater → Float
- Charater → Long
- ......
The target types are subclasses of Number. We can implement the following ConverterFactory:
public class CharacterToNumberFactory implements ConverterFactory<Character, Number> { @Override public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) { return new CharacterToNumber<>(targetType); } private static final class CharacterToNumber<T extends Number> implements Converter<Character, T> { private final Class<T> targetType; public CharacterToNumber(Class<T> targetType) { this.targetType = targetType; } @Override public T convert(Character source) { return NumberUtils.convertNumberToTargetClass((short) source.charValue(), this.targetType); } } }
In Spring, you can call it in the following ways:
- Create a CharacterToNumberFactory, which is managed by the container:
@Configuration public class AppConfig { @Bean public ConverterFactory<Character, Number> converter() { return new CharacterToNumberFactory(); } }
- Inject the ConverterFactory instance into the business:
@Service public class AppService { @Resource private ConverterFactory converterFactory; public void service() { Character character = new Character('a'); Converter integerConverter = converterFactory.getConverter(Integer.class); Object target = integerConverter.convert(character); System.out.println(target.getClass()); System.out.println(target); Converter doubleConverter = converterFactory.getConverter(Double.class); target = doubleConverter.convert(character); System.out.println(target.getClass()); System.out.println(target); } }
- After the service() method is called by the outside world, the following contents will be output, indicating that Date is successfully converted to String:
class java.lang.Integer 97 class java.lang.Double 97.0
Therefore, in essence, ConverterFactory is equivalent to managing a series of Converter factories with special hierarchical relationships, and is equivalent to the upgraded version of Converter.
When you need to convert the same S type into multiple T with hierarchical relationship, you can give priority to implementing ConverterFactory.
3 GenericConverter interface
org. springframework. core. convert. converter. The genericconverter interface specifies sourceType and targetType in the form of ConvertiblePair:
public interface GenericConverter { Set<ConvertiblePair> getConvertibleTypes(); Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType); final class ConvertiblePair { private final Class<?> sourceType; private final Class<?> targetType; } }
GenericConverter does not fix the number and types of sourceType and targetType in the implementation class. Compared with the Converter interface, GenericConverter has greater flexibility, but it is also more complex.
In the convert() method, you need to judge according to the types of sourceType and targetType and execute the corresponding conversion logic.
TypeDescriptor is used to describe the type information of sourceType and targetType:
public class TypeDescriptor implements Serializable { private final Class<?> type; private final ResolvableType resolvableType; private final TypeDescriptor.AnnotatedElementAdapter annotatedElement; }
For example, if we need to implement the type conversion of String → DataSize, we can follow the following example:
final class StringToDataSizeConverter implements GenericConverter { StringToDataSizeConverter() { } public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(String.class, DataSize.class)); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return ObjectUtils.isEmpty(source) ? null : this.convert(source.toString(), this.getDataUnit(targetType)); } private DataUnit getDataUnit(TypeDescriptor targetType) { DataSizeUnit annotation = (DataSizeUnit)targetType.getAnnotation(DataSizeUnit.class); return annotation != null ? annotation.value() : null; } private DataSize convert(String source, DataUnit unit) { return DataSize.parse(source, unit); } }
4 ConditionalConverter interface
org. springframework. core. convert. converter. The conditionalconverter interface provides a matches() method to judge whether the current Converter/GenericConverter/ConverterFactory can complete the conversion from sourceType to targetType.
public interface ConditionalConverter { boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); }
Since the ConditionalConverter interface only provides a pre verification method, it is more like an auxiliary function interface.
It is more used with other actual business interfaces, such as the above Converter/ConverterFactory/GenericConverter.
5 ConditionalGenericConverter interface
org. springframework. core. convert. converter. The conditionalgenericconverter interface integrates the functions of ConditionConverter and GenericConverter.
We can implement the functions of the above two interfaces by implementing the ConditionalGenericConverter interface.
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter { }