1. 概述
本文将深入探讨 Spring 的类型转换机制。Spring 开箱即用地提供了多种内置类型转换器,支持 String
、Integer
、Boolean
等基础类型之间的转换。除此之外,Spring 还提供了强大的类型转换 SPI,方便我们开发自定义转换器。
2. 内置转换器
先来看看 Spring 自带的转换器。以 String
转 Integer
为例:
@Autowired
ConversionService conversionService;
@Test
public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() {
assertThat(
conversionService.convert("25", Integer.class)).isEqualTo(25);
}
只需注入 Spring 提供的 ConversionService
,调用 convert()
方法即可。第一个参数是待转换值,第二个参数是目标类型。除了 String
转 Integer
,Spring 还支持大量其他类型组合。
3. 自定义转换器
现在看个更复杂的例子:将 String
表示的 Employee
转换为 Employee
对象。
Employee
类如下:
public class Employee {
private long id;
private double salary;
// 标准构造函数、getter/setter
}
String
格式为逗号分隔的 id
和 salary
(如 "1,50000.00")。要创建自定义转换器,需实现 Converter<S, T>
接口并重写 convert()
方法:
public class StringToEmployeeConverter
implements Converter<String, Employee> {
@Override
public Employee convert(String from) {
String[] data = from.split(",");
return new Employee(
Long.parseLong(data[0]),
Double.parseDouble(data[1]));
}
}
还没完!需要将 StringToEmployeeConverter
注册到 FormatterRegistry
。通过实现 WebMvcConfigurer
并重写 addFormatters()
方法:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
}
}
✅ 大功告成!新转换器已加入 ConversionService
,使用方式与内置转换器完全一致:
@Test
public void whenConvertStringToEmployee_thenSuccess() {
Employee employee = conversionService
.convert("1,50000.00", Employee.class);
Employee actualEmployee = new Employee(1, 50000.00);
assertThat(conversionService.convert("1,50000.00",
Employee.class))
.isEqualToComparingFieldByField(actualEmployee);
}
3.1. 隐式转换
除了显式调用 ConversionService
,Spring 还能在 Controller 方法中自动应用已注册的转换器:
@RestController
public class StringToEmployeeConverterController {
@GetMapping("/string-to-employee")
public ResponseEntity<Object> getStringToEmployee(
@RequestParam("employee") Employee employee) {
return ResponseEntity.ok(employee);
}
}
这是更自然的用法。测试一下效果:
@Test
public void getStringToEmployeeTest() throws Exception {
mockMvc.perform(get("/string-to-employee?employee=1,2000"))
.andDo(print())
.andExpect(jsonPath("$.id", is(1)))
.andExpect(jsonPath("$.salary", is(2000.0)))
}
测试会打印完整请求/响应信息。返回的 JSON 格式 Employee
对象如下:
{"id":1,"salary":2000.0}
4. 创建转换器工厂
还可以创建 ConverterFactory
按需生成转换器,这对 Enum
类型转换特别有用。
先看个简单的枚举:
public enum Modes {
ALPHA, BETA;
}
接下来创建 StringToEnumConverterFactory
,能生成任意 String
到 Enum
的转换器:
@Component
public class StringToEnumConverterFactory
implements ConverterFactory<String, Enum> {
private static class StringToEnumConverter<T extends Enum>
implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
@Override
public <T extends Enum> Converter<String, T> getConverter(
Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
}
⚠️ 注意:虽然用 Modes
演示,但工厂类本身是通用的,能按需为任意 Enum
类型生成转换器。
注册方式与之前相同:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
现在 ConversionService
已支持 String
到 Enum
的转换:
@Test
public void whenConvertStringToEnum_thenSuccess() {
assertThat(conversionService.convert("ALPHA", Modes.class))
.isEqualTo(Modes.ALPHA);
}
5. 创建通用转换器
GenericConverter
提供更灵活的通用转换能力,但会牺牲部分类型安全性。
以 Integer
/Double
/String
转 BigDecimal
为例。无需写三个转换器,一个 GenericConverter
即可搞定。
首先定义支持的转换类型组合:
public class GenericBigDecimalConverter
implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes () {
ConvertiblePair[] pairs = new ConvertiblePair[] {
new ConvertiblePair(Number.class, BigDecimal.class),
new ConvertiblePair(String.class, BigDecimal.class)};
return ImmutableSet.copyOf(pairs);
}
}
然后重写 convert()
方法:
@Override
public Object convert (Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (sourceType.getType() == BigDecimal.class) {
return source;
}
if(sourceType.getType() == String.class) {
String number = (String) source;
return new BigDecimal(number);
} else {
Number number = (Number) source;
BigDecimal converted = new BigDecimal(number.doubleValue());
return converted.setScale(2, BigDecimal.ROUND_HALF_EVEN);
}
}
convert()
方法逻辑简单,但 TypeDescriptor
提供了获取源/目标类型细节的强大能力。
最后注册转换器:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
registry.addConverter(new GenericBigDecimalConverter());
}
使用方式与其他转换器一致:
@Test
public void whenConvertingToBigDecimalUsingGenericConverter_thenSuccess() {
assertThat(conversionService
.convert(Integer.valueOf(11), BigDecimal.class))
.isEqualTo(BigDecimal.valueOf(11.00)
.setScale(2, BigDecimal.ROUND_HALF_EVEN));
assertThat(conversionService
.convert(Double.valueOf(25.23), BigDecimal.class))
.isEqualByComparingTo(BigDecimal.valueOf(Double.valueOf(25.23)));
assertThat(conversionService.convert("2.32", BigDecimal.class))
.isEqualTo(BigDecimal.valueOf(2.32));
}
6. 总结
本文通过多个示例演示了 Spring 类型转换系统的使用与扩展。关键点总结:
✅ 内置转换器:开箱即用,覆盖常见基础类型
✅ 自定义转换器:实现 Converter<S,T>
接口即可
✅ 隐式转换:Controller 参数自动应用转换器
✅ 转换器工厂:按需生成转换器,适合枚举等场景
✅ 通用转换器:灵活处理多类型转换,但需注意类型安全
完整源码可在 GitHub 获取。