译者注
原文
https://www.baeldung.com/spring-type-conversions
Demo
一、介绍
在这篇文章中,我们来学习Spring类型转换。
Spring为内建的类型提供了各种开箱即用的转换器,这意味着我们可以轻松的转换基本类型,例如String、Integer、Boolean或一些其他的类型。
除此之外,Spring也提供了一个固定的类型转换器SPI,以便我们用来自定义我们的转换器。
二、内建转换器
我们从Spring提供的开箱即用的转换器开始,先看看Spring内置的整数转换器:
@Autowired
ConversionService conversionService;
@Test
public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() {
assertThat(
conversionService.convert("25", Integer.class)).isEqualTo(25);
}
我们需要做的就是:自动装入(autowire)Spring提供的转换器服务ConversionService,并且调用这个转换方法convert()。第一个参数是“我们想要转换的值”,第二个参数是“我们想要转换的目标类型”
此外,还可以从Spring转换为Integer,还有多种不同的组合可以使用。
3. 创建自定义转换器
我们看一个转换的例子,在这个例子中,我们把代表Employee对象的字符串转换为Employee实例对象。
先给出Employee类:
public class Employee {
private long id;
private double salary;
// standard constructors, getters, setters
}
这个字符串的形式是“逗号分隔值”,分别表示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中,来告诉Spring我们用到了这个类。具体的做法就是实现WebMvcConfigurer类,并且重写Formatters()方法:
@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能在控制器方法中,使用所有已注册的转换器来实现隐式转换:
@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,这在创建枚举类型转换器的时候尤其有用。
我们来看一个简单的枚举:
public enum Modes {
ALPHA, BETA;
}
接下来,创建一个“字符串到枚举类型转换器工厂”StringToEnumConverterFactory,这个工厂可以创建字符串到枚举的转换器:
@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去演示用法,但我们在StringToEnumConverterFactory工厂的任何地方都没有提及这个枚举。我们这个工厂类会按需创建对于任何枚举类型的转换器。
下一步是注册这个工厂类,操作就像注册我们的Converter转换器一样,例子如下:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
现在这个转换服务ConversionService可以把字符串转换成枚举了:
@Test
public void whenConvertStringToEnum_thenSuccess() {
assertThat(conversionService.convert("ALPHA", Modes.class))
.isEqualTo(Modes.ALPHA);
}
5. 创建一个通用转换器
通用转换器GenericConverter让我们在牺牲安全性的前提下,获得更大的灵活性,来创建一个更通用的转换器。
我们来设计一个可以转换Integer、Double、String到BigDecimal的转换器。
不需要去写三个转换器,而是使用通用转换器GenericConverter来达到这个目的。
第一步是告诉Spring我们希望这个转换器支持什么类型。操作时创建一个ConvertiblePair的集合(每个元素是一对类型,表示可以转换):
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);
}
}
转换方法很简单,但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的类型转换系统,并且给出了一些示例。
和往常一样,你可以在Github上找到本文的示例代码。