Spring类型转换器相关接口和实现原理
源码见:mini-spring
Spring 的 类型转换(Type Conversion) 是指:在应用中将一种 Java 类型的对象转换成另一种类型对象的机制。
在实际开发中,我们经常会遇到“类型不匹配”问题,比如:
-
表单提交的字符串要转换成数字、日期、枚举等;
-
配置文件中的字符串要转换成 Boolean、List、Class;
-
Bean 属性注入时类型不一致;
-
SpEL 表达式中的值需要自动转换类型;
-
数据绑定(如 Web MVC 参数绑定)中,request 参数是字符串,要绑定到业务对象中。
Spring 提供了统一的类型转换框架来处理这些场景。
本节就Spring如何实现类型转换展开叙述,下节介绍如何将其融入生命周期当中
一、核心组件
1. ConversionService
(核心接口)
Spring 类型转换的核心服务接口,负责执行类型之间的转换:
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
}
- 可以检查是否支持某种转换。
- 可以执行实际的转换。
2. Converter<S, T>
这是最基本的类型转换器接口,Spring 中的所有类型转换器都实现了该接口:
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
}
S
:源类型T
:目标类型
例如,将字符串转为整数的转换器:
public class StringToIntegerConverter implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
3. GenericConverter
更灵活但也更复杂的转换器,适合处理多个源/目标类型的场景:
public class StringToIntegerConverter implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
-
支持多个源类型与目标类型组合。
-
使用
TypeDescriptor
解决泛型、注解等更复杂的类型问题。
4. ConverterFactory<S, R>
适用于一组相关类型转换的情况。例如:String
转为 Enum
的所有子类型:
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
二、底层实现
在上一节中,我们简要了解了类型转换的核心组件。实际上,Spring 提供了三种不同类型的转换器接口:
- 普通类型转换器(
Converter
) - 类型转换工厂(
ConverterFactory
) - 通用类型转换器(
GenericConverter
)
下面将逐一进行讲解。
1,普通类型转换器 Converter<S, T>
public interface Converter<S,T> {
T convert(S source);
}
该接口结构非常简洁,只定义了一个核心方法 convert
,用于将源类型 S
转换为目标类型 T
。使用时只需实现此接口并提供具体的转换逻辑即可。
例如,将 String
转换为 Integer
:
public class StringToIntegerConverter implements Converter<String,Integer> {
@Override
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
测试一下
@Test
public void testStringToIntegerConverter(){
StringToIntegerConverter converter = new StringToIntegerConverter();
Integer integer = converter.convert("10");
Assert.assertEquals(Integer.valueOf(10),integer);
}
2,类型转换工厂 ConverterFactory<S, R>
相较于 Converter
接口,ConverterFactory
适用于更广泛的转换需求。它可以通过工厂方法,为某一源类型 S
生成多个不同目标子类型 R
的转换器,常用于统一处理一组目标类型。
public interface ConverterFactory<S, R> {
/**
* 根据目标类型获取转换器对象
*
* @param <T> 目标类型参数,表示具体的转换后数据类型,必须是R类型或其子类型
* @param targetType 目标类型的Class对象,用于指定转换后的数据类型
* @return 返回一个Converter对象,用于将源数据类型S转换为目标类型T
*/
<T extends R> Converter<S,T> getConverter(Class<T> targetType);
}
以下是一个将 String
转换为多个数字类型(如 Integer
、Long
)的工厂实现:
public class StringToNumberConverterFactory implements ConverterFactory<String,Number> {
/**
* 根据目标类型获取转换器对象
*
* @param targetType 目标类型的Class对象,用于指定转换后的数据类型
* @return 返回一个Converter对象,用于将源数据类型S转换为目标类型T
*/
@Override
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToNumber<T>(targetType);
}
public static final class StringToNumber<T extends Number> implements Converter<String,T>{
private final Class<T> targetType;
public StringToNumber(Class<T> targetType) {
this.targetType = targetType;
}
@Override
public T convert(String source) {
if (source.length() == 0){
return null;
}
if (targetType.equals(Integer.class)){
return (T) Integer.valueOf(source);
} else if (targetType.equals(Long.class)) {
return (T) Long.valueOf(source);
}else {
throw new IllegalArgumentException(
"Cannot convert String [" + source + "] to target class [" + targetType.getName() + "]");
}
}
}
}
测试一下
@Test
public void testStringToNumberConverterFactory(){
StringToNumberConverterFactory converterFactory = new StringToNumberConverterFactory();
Converter<String, Integer> converter = converterFactory.getConverter(Integer.class);
Integer integer = converter.convert("10");
Assert.assertEquals(Integer.valueOf(10),integer);
}
3,通用类型转换器 GenericConverter
GenericConverter
是 Spring 提供的最灵活、功能最强大的转换接口,适用于更复杂的场景,包括:
- 支持多个源类型和目标类型的转换(如
String → List<Integer>
、String → Set<String>
); - 利用运行时类型信息(通过
TypeDescriptor
)进行精确判断; - 适配泛型、注解、字段等更复杂的上下文信息。
public interface GenericConverter {
// 执行类型转换逻辑
Object convert(Object source, Class sourceType, Class targetType);
// 获取到对应的ConvertiblePair配对关系对象
Set<ConvertiblePair> getConvertibleTypes();
/**
* 用于管理“源类型”和“目标类型”的配对关系
*/
public static final class ConvertiblePair{
private final Class<?> sourceType;
private final Class<?> targetType;
public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
this.sourceType = sourceType;
this.targetType = targetType;
}
public Class<?> getSourceType() {
return sourceType;
}
public Class<?> getTargetType() {
return targetType;
}
@Override
public boolean equals(Object object) {
if (object == null || getClass() != object.getClass()) return false;
ConvertiblePair that = (ConvertiblePair) object;
return Objects.equals(sourceType, that.sourceType) && Objects.equals(targetType, that.targetType);
}
@Override
public int hashCode() {
return Objects.hash(sourceType, targetType);
}
}
}
我们可以看到在GenericConverter我们还定义了一个静态的内部类ConvertiblePair,该类是用来用于管理“源类型”和“目标类型”的配对关系。
简单来说,我们来思考一个问题当我们调用GenericConverter的convert去尝试转换类型,而GenericConverter是一个接口,具体的各种转换逻辑由子类实现,那我们又如何才能判断出,我要转换的类型是否合法,当前类型转换器当中是否支持?
所以这里就引入了ConvertiblePair用来表示一个“源类型 → 目标类型”的一组转换类型
测试一下
@Test
public void testGenericConverter(){
StringToBooleanConverter converter = new StringToBooleanConverter();
Boolean aTrue =(Boolean) converter.convert("true", String.class, Boolean.class);
Assert.assertTrue(aTrue);
}