Перейти к основному содержимому

Руководство по преобразованию типов Spring

· 5 мин. чтения

1. Введение

В этой статье мы рассмотрим преобразования типов Spring.

Spring предоставляет готовые различные преобразователи для встроенных типов; это означает преобразование в/из базовых типов, таких как String, Integer, Boolean и ряд других типов.

Помимо этого, Spring также предоставляет SPI преобразования твердого типа для разработки наших пользовательских преобразователей.

2. Встроенный преобразователь

Мы начнем с готовых преобразователей, доступных в Spring; давайте посмотрим на преобразование строки в целое число :

@Autowired
ConversionService conversionService;

@Test
public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() {
assertThat(
conversionService.convert("25", Integer.class)).isEqualTo(25);
}

Единственное, что нам нужно сделать здесь, это автоматически связать ConversionService , предоставляемый Spring, и вызвать метод convert() . Первый аргумент — это значение, которое мы хотим преобразовать, а второй аргумент — это целевой тип, в который мы хотим преобразовать.

Помимо этого примера преобразования строки в целое число , нам доступно множество других комбинаций.

3. Создание пользовательского конвертера

Давайте рассмотрим пример преобразования строкового представления Employee в экземпляр Employee .

Вот класс Сотрудник :

public class Employee {

private long id;
private double salary;

// standard constructors, getters, setters
}

Строка будет разделенной запятой парой, представляющей идентификатор и зарплату . Например, «1,50000,00».

Чтобы создать наш собственный Converter , нам нужно реализовать интерфейс 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]));
}
}

Мы еще не закончили. Нам также нужно сообщить Spring об этом новом конвертере, добавив StringToEmployeeConverter в FormatterRegistry . Это можно сделать, реализовав WebMvcConfigurer и переопределив метод addFormatters() :

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
}
}

Вот и все. Наш новый Converter теперь доступен для ConversionService , и мы можем использовать его так же, как и любой другой встроенный Converter :

@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);
}
}

Это более естественный способ использования Converter s. Давайте добавим тест, чтобы увидеть его в действии:

@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)))
}

Как видите, тест распечатает все детали запроса, а также ответ. Вот объект Employee в формате JSON, который возвращается как часть ответа:

{"id":1,"salary":2000.0}

4. Создание ConverterFactory

Также можно создать ConverterFactory , который создает Converter по запросу. Это особенно полезно при создании Converter для Enums .

Давайте посмотрим на действительно простой Enum:

public enum Modes {
ALPHA, BETA;
}

Затем давайте создадим StringToEnumConverterFactory , который может генерировать Converter для преобразования 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);
}
}

Как мы видим, фабричный класс внутренне использует реализацию интерфейса Converter .

Здесь следует отметить, что, хотя мы будем использовать наш Modes Enum для демонстрации использования, мы нигде не упоминали Enum в StringToEnumConverterFactory . Наш фабричный класс достаточно универсален, чтобы генерировать Converter по запросу для любого типа 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 предоставляет нам больше гибкости для создания Converter для более универсального использования за счет потери некоторой безопасности типов.

Давайте рассмотрим пример преобразования Integer , Double или String в значение BigDecimal . Для этого нам не нужно писать три Converter . Простой 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);
}
}

Метод 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 на различных примерах.

Как всегда, полный исходный код этой статьи можно найти на GitHub .