Ръководство за преобразувания от пролетен тип

1. Въведение

В тази статия ще разгледаме преобразуванията на типа на Spring.

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

Отделно от това, Spring предлага и солиден SPI тип за преобразуване за разработване на нашите персонализирани конвертори.

2. Вграден конвертор s

Ще започнем с конверторите, които са налични през пролетта; нека да разгледаме преобразуването String to Integer :

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

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

Освен този String to Integer пример, има много други различни комбинации, налични за нас.

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

Нека да разгледаме един пример за преобразуване на String представяне на служител в екземпляр на служител .

Ето класа на служителите :

public class Employee { private long id; private double salary; // standard constructors, getters, setters }

В String ще бъдат разделени със запетая двойка представлява ID и заплати. Например „1,50000.00“.

За да създадем нашия персонализиран конвертор , трябва да внедрим интерфейса на конвертора и да приложим метода 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 за този нов конвертор, като добавим StringToEfficieeConverter към 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 също е способен да имплицитно преобразува стойности точно в методите на контролера за всички регистрирани преобразуватели:

@RestController public class StringToEmployeeConverterController { @GetMapping("/string-to-employee") public ResponseEntity 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 s при поискване. Това е особено полезно при създаването на Converter s за Enums .

Нека да разгледаме един наистина прост Enum:

public enum Modes { ALPHA, BETA; }

След това нека създадем StringToEnumConverterFactory, който може да генерира Converter s за конвертиране на 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); } }

Както виждаме, фабричният клас използва вътрешно изпълнение на конверторния интерфейс.

Тук трябва да отбележим, че въпреки че ще използваме нашия Modes Enum, за да демонстрираме използването, никъде не сме споменали Enum в StringToEnumConverterFactory . Нашият фабричен клас е достатъчно общ, за да генерира конверторите при поискване за всеки тип Enum .

Следващата стъпка е да регистрирате този фабричен клас, както регистрирахме нашия конвертор в предишния пример:

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

Сега ConversionService е готов да конвертира String s в Enum s:

@Test public void whenConvertStringToEnum_thenSuccess() { assertThat(conversionService.convert("ALPHA", Modes.class)) .isEqualTo(Modes.ALPHA); }

5. Създаване на GenericConverter

А GenericConverter ни дава по-голяма гъвкавост, за да създадете Converter по-общо използване с цената на загуба на някои безопасност тип.

Нека разгледаме пример за преобразуване на цяло число , двойно или низ в стойност BigDecimal.Не е нужно да пишем три конвертора s за това. Един прост GenericConverter може да служи на целта.

Първата стъпка е да кажете на Spring какви видове преобразувания се поддържат. Правим това, като създаваме набор от 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 ни предоставя голяма гъвкавост по отношение на получаването на подробности относно източника и целевия тип.

Както вече се досещате, следващата стъпка е да регистрирате този конвертор :

@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.