Персонализирано свързващо устройство за данни през пролетта MVC

1. Общ преглед

Тази статия ще покаже как можем да използваме механизма за обвързване на данни на Spring, за да направим нашия код по-ясен и четим чрез прилагане на автоматични примитиви към преобразуване на обекти.

По подразбиране Spring знае само как да конвертира прости типове. С други думи, след като изпратим данни на данните от контролера Int , String или Boolean , те автоматично ще бъдат обвързани със съответните типове Java.

Но в реални проекти това няма да е достатъчно, тъй като може да се наложи да обвържем по-сложни типове обекти .

2. Обвързване на отделни обекти с искане на параметри

Нека започнем просто и първо да обвържем прост тип; ще трябва да осигурим персонализирана реализация на интерфейса на конвертора, където S е типът, от който конвертираме, а T е типът, в който конвертираме:

@Component public class StringToLocalDateTimeConverter implements Converter { @Override public LocalDateTime convert(String source) { return LocalDateTime.parse( source, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } }

Сега можем да използваме следния синтаксис в нашия контролер:

@GetMapping("/findbydate/{date}") public GenericEntity findByDate(@PathVariable("date") LocalDateTime date) { return ...; }

2.1. Използване на Enums като параметри на заявката

След това ще видим как да използваме e num като RequestParameter .

Тук имаме прости режими на изброяване :

public enum Modes { ALPHA, BETA; }

Ще изградим String to enum Converter, както следва:

public class StringToEnumConverter implements Converter { @Override public Modes convert(String from) { return Modes.valueOf(from); } }

След това трябва да регистрираме нашия конвертор :

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToEnumConverter()); } }

Сега можем да използваме нашия Enum като RequestParameter :

@GetMapping public ResponseEntity getStringToMode(@RequestParam("mode") Modes mode) { // ... }

Или като променлива на пътя :

@GetMapping("/entity/findbymode/{mode}") public GenericEntity findByEnum(@PathVariable("mode") Modes mode) { // ... }

3. Обвързване на йерархия на обекти

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

В този пример имаме AbstractEntity нашия основен клас:

public abstract class AbstractEntity { long id; public AbstractEntity(long id){ this.id = id; } }

И подкласовете Foo and Bar :

public class Foo extends AbstractEntity { private String name; // standard constructors, getters, setters }
public class Bar extends AbstractEntity { private int value; // standard constructors, getters, setters }

В този случай можем да реализираме ConverterFactory, където S ще бъде типът, от който конвертираме, а R да бъде основният тип, определящ диапазона от класове, в които можем да конвертираме:

public class StringToAbstractEntityConverterFactory implements ConverterFactory{ @Override public  Converter getConverter(Class targetClass) { return new StringToAbstractEntityConverter(targetClass); } private static class StringToAbstractEntityConverter implements Converter { private Class targetClass; public StringToAbstractEntityConverter(Class targetClass) { this.targetClass = targetClass; } @Override public T convert(String source) { long id = Long.parseLong(source); if(this.targetClass == Foo.class) { return (T) new Foo(id); } else if(this.targetClass == Bar.class) { return (T) new Bar(id); } else { return null; } } } }

Както виждаме, единственият метод, който трябва да внедри, е getConverter (), който връща конвертор за необходимия тип. След това процесът на преобразуване се делегира на този преобразувател.

След това трябва да регистрираме нашата ConverterFactory :

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new StringToAbstractEntityConverterFactory()); } }

И накрая, можем да го използваме, както ни харесва в нашия контролер:

@RestController @RequestMapping("/string-to-abstract") public class AbstractEntityController { @GetMapping("/foo/{foo}") public ResponseEntity getStringToFoo(@PathVariable Foo foo) { return ResponseEntity.ok(foo); } @GetMapping("/bar/{bar}") public ResponseEntity getStringToBar(@PathVariable Bar bar) { return ResponseEntity.ok(bar); } }

4. Обвързване на домейн обекти

Има случаи, когато искаме да обвържем данни с обекти, но те идват или по недиректен начин (например от променливи Session , Header или Cookie ) или дори се съхраняват в източник на данни. В тези случаи трябва да използваме различно решение.

4.1. Разрешител за потребителски аргументи

На първо място, ще дефинираме анотация за такива параметри:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Version { }

След това ще внедрим персонализиран HandlerMethodArgumentResolver:

public class HeaderVersionArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterAnnotation(Version.class) != null; } @Override public Object resolveArgument( MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest(); return request.getHeader("Version"); } }

Последното нещо е да дадем на Spring да знае къде да ги търси:

@Configuration public class WebConfig implements WebMvcConfigurer { //... @Override public void addArgumentResolvers( List argumentResolvers) { argumentResolvers.add(new HeaderVersionArgumentResolver()); } }

Това е. Сега можем да го използваме в контролер:

@GetMapping("/entity/{id}") public ResponseEntity findByVersion( @PathVariable Long id, @Version String version) { return ...; }

Както можем да видим, HandlerMethodArgumentResolver е resolveArgument () метод на възвращаемостта на даден обект. С други думи, бихме могли да върнем всеки обект, не само String .

5. Заключение

В резултат на това се отървахме от много рутинни преобразувания и оставихме Spring да прави повечето неща вместо нас. В края нека заключим:

  • За индивидуална прост тип до реализации обект ние трябва да използват Converter изпълнение
  • За капсулиране на логиката на преобразуване за редица обекти можем да опитаме изпълнението на ConverterFactory
  • За всички данни, които идват индиректно или се изисква да се приложи допълнителна логика за извличане на свързаните данни, по-добре е да се използва HandlerMethodArgumentResolver

Както обикновено, всички примери винаги могат да бъдат намерени в нашето хранилище на GitHub.