1. Introduction
1.介绍
In this article, we’ll have a look at Spring’s type conversions.
在这篇文章中,我们将看一下Spring的类型转换。
Spring provides out-of-the-box various converters for built-in types; this means converting to/from basic types like String, Integer, Boolean and a number of other types.
Spring为内置类型提供了开箱即用的各种转换器;这意味着与String、Integer、Boolean等基本类型的转换以及其他一些类型的转换。
Apart from this, Spring also provides a solid type conversion SPI for developing our custom converters.
除此以外,Spring还提供了一个坚实的类型转换SPI,用于开发我们的定制转换器。
2. Built-in Converters
2.内置转换器s
We’ll start with the converters available out-of-the-box in Spring; let’s have a look at the String to Integer conversion:
我们将从Spring中开箱即用的转换器开始;让我们看看String到Integer的转换。
@Autowired
ConversionService conversionService;
@Test
public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() {
assertThat(
conversionService.convert("25", Integer.class)).isEqualTo(25);
}
The only thing we need to do here is to autowire the ConversionService provided by Spring and call the convert() method. The first argument is the value that we want to convert and the second argument is the target type that we want to convert to.
我们在这里唯一需要做的是自动连接Spring提供的ConversionService并调用convert()方法。第一个参数是我们要转换的值,第二个参数是我们要转换的目标类型。
Apart from this String to Integer example, there’re a lot of various other combinations available for us.
除了这个String到Integer的例子之外,还有很多其他各种组合可供我们选择。
3. Creating a Custom Converter
3.创建一个自定义的转换器
Let’s have a look at an example of converting a String representation of an Employee to an Employee instance.
让我们看一下将String表示的Employee转换为Employee实例的一个例子。
Here’s the Employee class:
这里是Employee类。
public class Employee {
private long id;
private double salary;
// standard constructors, getters, setters
}
The String will be a comma-separated pair representing id and salary. For example, “1,50000.00”.
字符串将是一个逗号分隔的对,代表id和salary。例如,”1,50000.00″。
In order to create our custom Converter, we need to implement the Converter<S, T> interface and implement the convert() method:
为了创建我们的自定义转换器,我们需要实现转换器<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]));
}
}
We’re not done yet. We also need to tell Spring about this new converter by adding the StringToEmployeeConverter to the FormatterRegistry. This can be done by implementing the WebMvcConfigurer and overriding addFormatters() method:
我们还没有完成。我们还需要将StringToEmployeeConverter添加到FormatterRegistry中,从而告诉Spring这个新转换器。这可以通过实现WebMvcConfigurer和覆盖addFormatters()方法来完成。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
}
}
And that’s it. Our new Converter is now available to the ConversionService and we can use it in the same way as any other built-in Converter:
就这样了。我们的新转换器现在可用于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. Implicit Conversion
3.1.隐式转换
Beyond these explicit conversion using the ConversionService, Spring is also capable of implicitly converting values right in Controller methods for all registered converters:
除了这些使用ConversionService的显式转换之外,Spring还能够在Controller方法中直接隐式转换值所有注册的转换器。
@RestController
public class StringToEmployeeConverterController {
@GetMapping("/string-to-employee")
public ResponseEntity<Object> getStringToEmployee(
@RequestParam("employee") Employee employee) {
return ResponseEntity.ok(employee);
}
}
This is a more natural way of using the Converters. Let’s add a test to see it in action:
这是一种更自然的使用Converters的方式。让我们添加一个测试来看看它的作用。
@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)))
}
As you can see, the test will print all the details of the request as well as the response. Here is the Employee object in JSON format that is returned as part of the response:
正如你所看到的,该测试将打印请求的所有细节以及响应。这里是JSON格式的Employee对象,作为响应的一部分被返回。
{"id":1,"salary":2000.0}
4. Creating a ConverterFactory
4.创建一个ConverterFactory
It’s also possible to create a ConverterFactory that creates Converters on demand. This is particularly helpful in creating Converters for Enums.
我们也可以创建一个ConverterFactory,按需创建Converters。这对于为Enums创建Converters特别有帮助。
Let’s have a look at a really simple Enum:
让我们来看看一个非常简单的枚举。
public enum Modes {
ALPHA, BETA;
}
Next, let’s create a StringToEnumConverterFactory that can generate Converters for converting a String to any Enum:
接下来,让我们创建一个StringToEnumConverterFactory,它可以生成Converters,将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);
}
}
As we can see, the factory class internally uses an implementation of Converter interface.
我们可以看到,工厂类内部使用了Converter接口的实现。
One thing to note here is that although we’ll use our Modes Enum to demonstrate the usage, we haven’t mentioned the Enum anywhere in the StringToEnumConverterFactory. Our factory class is generic enough to generate the Converters on demand for any Enum type.
这里需要注意的是,尽管我们将使用我们的Modes Enum来演示用法,但我们没有在StringToEnumConverterFactory的任何地方提及Enum。我们的工厂类足够通用,可以根据需要为任何Enum类型生成Converters。
The next step is to register this factory class as we registered our Converter in the previous example:
下一步是注册这个工厂类,就像我们在前面的例子中注册我们的Converter一样。
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
Now the ConversionService is ready to convert Strings to Enums:
现在ConversionService已经准备好将Strings转换成Enums。
@Test
public void whenConvertStringToEnum_thenSuccess() {
assertThat(conversionService.convert("ALPHA", Modes.class))
.isEqualTo(Modes.ALPHA);
}
5. Creating a GenericConverter
5.创建一个GenericConverter
A GenericConverter provides us more flexibility to create a Converter for a more generic use at the cost of losing some type safety.
一个GenericConverter为我们提供了更多的灵活性,使我们可以为更通用的用途创建一个Converter,代价是失去一些类型安全。
Let’s consider an example of converting an Integer, Double, or a String to a BigDecimal value.We don’t need to write three Converters for this. A simple GenericConverter could serve the purpose.
让我们考虑一个将Integer、Double或String转换为BigDecimal值的例子。我们不需要为此编写三个Converters。一个简单的GenericConverter就能达到这个目的。
The first step is to tell Spring what types of conversion are supported. We do this by creating a Set of ConvertiblePair:
第一步是告诉Spring支持哪些类型的转换。我们通过创建一个Set的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);
}
}
The next step is to override the convert() method in the same class:
下一步是在同一个类中覆盖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);
}
}
The convert() method is as simple as it can be. However, the TypeDescriptor provides us great flexibility in terms of getting the details concerning the source and the target type.
convert()方法是尽可能简单的。然而,TypeDescriptor在获取有关源和目标类型的细节方面为我们提供了极大的灵活性。
As you might have already guessed, the next step is to register this Converter:
你可能已经猜到了,下一步是注册这个Converter。
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
registry.addConverter(new GenericBigDecimalConverter());
}
Using this Converter is similar to the other examples that we’ve already seen:
使用这个Converter与我们已经看到的其他例子类似。
@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. Conclusion
6.结论
In this tutorial, we’ve seen how to use and extend Spring’s type conversion system with various examples.
在本教程中,我们已经看到了如何通过各种实例来使用和扩展Spring的类型转换系统。
As always, the full source code for this article can be found over on GitHub.
一如既往,本文的完整源代码可以在GitHub上找到over。