类型转换本质上来说是这么一个过程:SourceType
--> TargetType
Spring提供了一套基于Converter
接口的SPI(Server Provide Interface)机制。
通过实现Converter
接口,我们可以根据自己的业务需求制定特定的类型转换规则。
1 Converter
接口
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);
};
}
}
泛型解释:
-
S
:原始类型。 -
T
:目标类型。
方法解释:
-
convert()
:将数据由S
类型转换到T
类型。 -
andThen()
:链式类型转换,S
→T
→U
。
例如,我们想要实现一个Object
→ String
的类型转换,可以按以下步骤进行:
- 实现
Converter
接口,指定泛型S
为Object
,泛型T
为String
; - 实现
convert()
方法,编写转换规则。
public class ObjectToStringConverter implements Converter<Object, String> {
@Override
public String convert(Object source) {
return source.toString();
}
}
在Spring中则可以通过以下方式调用:
- 创建
ObjectToStringConverter
,交由容器管理:
@Configuration
public class AppConfig {
@Bean
public Converter converter() {
return new ObjectToStringConverter();
}
}
- 在业务中注入
Converter
实例:
@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);
}
}
- 外界调用
service()
方法后,会输出以下内容,说明Date
成功转换成String
:
class java.lang.String
Thu Jan 13 21:33:49 CST 2022
Spring的类型转换机制本质上就是这么一个套路。通过实现Converter
接口,可以针对性的实现不同的类型转换规则,适用于不同的业务场景。
当然,Spring也实现了许多常用的XxxConverter
,需要进行类型转换时,可以先看看Converter
的继承层次,是否可以直接使用。
2 ConverterFactory
接口
为了便于管理一系列具有层次关系的Converter
,Spring还提供了ConverterFactory
接口。
org.springframework.core.convert.converter.ConverterFactory
:
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
泛型解释:
-
S
:原始类型。 -
R
:目标类型的顶层父类/接口。 -
T
:特定目标类型,是R
的子类/实现类。
方法解释:
-
getConverter()
:获取目标类型为T
的Converter
实现类。
例如,如果有以下业务需求:
-
Charater
→Integer
-
Charater
→Float
-
Charater
→Long
- ……
目标类型都是Number
的子类,我们可以实现如下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);
}
}
}
在Spring中则可以通过以下方式调用:
- 创建
CharacterToNumberFactory
,交由容器管理:
@Configuration
public class AppConfig {
@Bean
public ConverterFactory<Character, Number> converter() {
return new CharacterToNumberFactory();
}
}
- 在业务中注入
ConverterFactory
实例:
@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);
}
}
- 外界调用
service()
方法后,会输出以下内容,说明Date
成功转换成String
:
class java.lang.Integer
97
class java.lang.Double
97.0
所以说,本质上ConverterFactory
相当于是管理一系列具有特殊层次关系的Converter
的工厂,相当于是Converter
的升级版本。
在需要将同一个S
类型转换成多个具有层次关系的T
时,可以优先考虑实现ConverterFactory
。
3 GenericConverter
接口
org.springframework.core.convert.converter.GenericConverter
接口以ConvertiblePair
的形式指定sourceType
和targetType
:
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
不会在实现类中固定sourceType
和targetType
的数量和类型,相比于Converter
接口有了更大的灵活度,但也更加复杂。
convert()
方法中,需要根据sourceType
和targetType
的类型进行判断,执行对应的转换逻辑。
TypeDescriptor
用来描述sourceType
和targetType
的类型信息:
public class TypeDescriptor implements Serializable {
private final Class<?> type;
private final ResolvableType resolvableType;
private final TypeDescriptor.AnnotatedElementAdapter annotatedElement;
}
举个例子,如果我们需要实现String
→ DataSize
的类型转换,可以按以下例子进行:
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
接口
org.springframework.core.convert.converter.ConditionalConverter
接口提供了matches()
方法,用来判断当前Converter
/GenericConverter
/ConverterFactory
能否完成从sourceType
到targetType
的转换。
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
由于ConditionalConverter
接口只提供了一个预校验的方法,它更像是一个辅助性功能接口。
它更多的跟其他实际业务接口配合使用,比如上述的Converter
/ConverterFactory
/GenericConverter
等。
5 ConditionalGenericConverter
接口
org.springframework.core.convert.converter.ConditionalGenericConverter
接口将ConditionConverter
和GenericConverter
的功能整合了起来。
我们实现ConditionalGenericConverter
接口就可以实现上述两个接口的功能。
public interface ConditionalGenericConverter
extends GenericConverter, ConditionalConverter {
}