Руководство по преобразованию типов пружин
1. Вступление
В этой статье мы рассмотрим преобразование типов в Spring.
Spring предоставляет "из коробки" различные преобразователи для встроенных типов; это означает преобразование в / из базовых типов, таких какString, Integer, Boolean и ряда других типов.
Помимо этого, Spring также предоставляет SPI для конвертации типов для разработки наших пользовательских конвертеров.
2. ВстроенныйConverters
Мы начнем с конвертеров, которые будут доступны уже весной; Давайте посмотрим на преобразованиеString вInteger:
@Autowired
ConversionService conversionService;
@Test
public void whenConvertStringToIntegerUsingDefaultConverter_thenSuccess() {
assertThat(
conversionService.convert("25", Integer.class)).isEqualTo(25);
}
Единственное, что нам нужно сделать здесь, это автоматически подключитьConversionService, предоставленные Spring, и вызвать методconvert(). Первый аргумент - это значение, которое мы хотим преобразовать, а второй аргумент - это целевой тип, в который мы хотим преобразовать.
Помимо этого примера отString доInteger, нам доступно множество различных комбинаций.
3. Создание пользовательскогоConverter
Давайте посмотрим на пример преобразованияString представленияEmployee в экземплярEmployee.
Вот классEmployee:
public class Employee {
private long id;
private double salary;
// standard constructors, getters, setters
}
String будет разделенной запятыми парой, представляющейid иsalary.. Например, «1,50000.00».
Чтобы создать наш собственныйConverter, нам нужно реализовать интерфейсConverter<S, T> и реализовать методconvert():
public class StringToEmployeeConverter
implements Converter {
@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 is also capable of implicitly converting values right in Controller methods для всех зарегистрированных преобразователей:
@RestController
public class StringToEmployeeConverterController {
@GetMapping("/string-to-employee")
public ResponseEntity
Это более естественный способ использования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)))
}
Как видите, тест напечатает все детали запроса, а также ответ. Вот объектEmployee в формате JSON, который возвращается как часть ответа:
{"id":1,"salary":2000.0}
4. СозданиеConverterFactory
Также возможно создатьConverterFactory, который создаетConverters по запросу. Это особенно полезно при созданииConverters дляEnums.
Давайте посмотрим на действительно простой Enum:
public enum Modes {
ALPHA, BETA;
}
Затем давайте создадимStringToEnumConverterFactory, который может генерироватьConverters для преобразованияString в любойEnum:
@Component
public class StringToEnumConverterFactory
implements ConverterFactory {
private static class StringToEnumConverter
implements Converter {
private Class enumType;
public StringToEnumConverter(Class enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
@Override
public Converter getConverter(
Class targetType) {
return new StringToEnumConverter(targetType);
}
}
Как мы видим, фабричный класс внутренне использует реализацию интерфейсаConverter.
Здесь следует отметить, что хотя мы будем использовать нашModes Enum для демонстрации использования, мы не упомянулиEnum где-либо вStringToEnumConverterFactory. Our factory class is generic enough to generate the Converters on demand for any Enum type.
Следующим шагом является регистрация этого фабричного класса, как мы зарегистрировали нашConverter в предыдущем примере:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
ТеперьConversionService готов преобразоватьStrings вEnums:
@Test
public void whenConvertStringToEnum_thenSuccess() {
assertThat(conversionService.convert("ALPHA", Modes.class))
.isEqualTo(Modes.ALPHA);
}
5. СозданиеGenericConverter
GenericConverter дает нам больше гибкости для созданияConverter для более общего использования за счет потери некоторой безопасности типов.
Рассмотрим пример преобразованияInteger,Double илиString в значениеBigDecimal. Для этого нам не нужно писать триConverters . Этой цели может служить простойGenericConverter.
Первый шаг - сообщить Spring, какие типы конвертации поддерживаются. Мы делаем это, создаваяSet изConvertiblePair:
public class GenericBigDecimalConverter
implements GenericConverter {
@Override
public Set 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 предоставляет нам большую гибкость с точки зрения получения подробной информации об источнике и типе цели.
Как вы уже догадались, следующим шагом будет регистрация этогоConverter:
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEmployeeConverter());
registry.addConverterFactory(new StringToEnumConverterFactory());
registry.addConverter(new GenericBigDecimalConverter());
}
Использование этого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. Заключение
В этом руководстве мы увидели, как использовать и расширять систему преобразования типов Spring с помощью различных примеров.
Как всегда, полный исходный код этой статьи можно найти наover on GitHub.