1. Обзор
В этой статье будет показано, как мы можем использовать механизм привязки данных Spring, чтобы сделать наш код более понятным и читаемым, применяя автоматические примитивы к преобразованиям объектов.
По умолчанию Spring знает только, как конвертировать простые типы. Другими словами, как только мы передадим данные контроллеру Int , String или Boolean тип данных, он будет автоматически привязан к соответствующим типам Java.
Но в реальных проектах этого будет недостаточно, так как нам может потребоваться связать более сложные типы объектов .
2. Привязка отдельных объектов к параметрам запроса
Давайте начнем с простого и сначала свяжем простой тип; нам нужно будет предоставить пользовательскую реализацию интерфейса Converter T> , где S – это тип, из которого мы преобразуем, и T – это тип, в который мы преобразуем: 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. Использование перечислений в качестве параметров запроса
Далее мы увидим как использовать e num в качестве параметра Запроса .
Здесь у нас есть простое перечисление Режимы :
public enum Modes { ALPHA, BETA; }
Мы построим конвертер String to enum следующим образом:
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()); } }
Теперь мы можем использовать наше Перечисление в качестве параметра Запроса :
@GetMapping public ResponseEntity
Или в качестве переменной Path :
@GetMapping("/entity/findbymode/{mode}") public GenericEntity findByEnum(@PathVariable("mode") Modes mode) { // ... }
3. Привязка иерархии объектов
Иногда нам нужно преобразовать все дерево иерархии объектов, и имеет смысл иметь более централизованную привязку, а не набор отдельных преобразователей.
В этом примере у нас есть Абстрактность наш базовый класс:
public abstract class AbstractEntity { long id; public AbstractEntity(long id){ this.id = id; } }
И подклассы Foo и 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 }
В этом случае мы можем реализовать Фабрику конвертеров R> где S будет типом, из которого мы преобразуем, а R будет базовым типом определяющим диапазон классов, в которые мы можем преобразовать: 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 () , который возвращает конвертер для нужного типа. Затем процесс преобразования делегируется этому конвертеру.
Затем нам нужно зарегистрировать наш Конвертерный завод :
@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
4. Привязка Объектов Домена
Бывают случаи, когда мы хотим привязать данные к объектам, но они поступают либо не прямым способом (например, из Сеанса , заголовка или 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"); } }
Последнее, что нужно сделать, – это сообщить Весне, где их искать:
@Configuration public class WebConfig implements WebMvcConfigurer { //... @Override public void addArgumentResolvers( ListargumentResolvers) { argumentResolvers.add(new HeaderVersionArgumentResolver()); } }
Вот и все. Теперь мы можем использовать его в контроллере:
@GetMapping("/entity/{id}") public ResponseEntity findByVersion( @PathVariable Long id, @Version String version) { return ...; }
Как мы видим, HandlerMethodArgumentResolver ‘s resolveArgument() метод возвращает Объект. Другими словами, мы могли бы вернуть любой объект, а не только String .
5. Заключение
В результате мы избавились от многих рутинных преобразований и позволили Spring делать большинство вещей за нас. В конце концов, давайте заключим:
- Для отдельного простого преобразования типа в объект мы должны использовать Конвертер реализацию
- Для инкапсуляции логики преобразования для ряда объектов мы можем попробовать Фабрику конвертеров реализацию
- Для любых данных, поступающих косвенно или требуется применить дополнительную логику для извлечения связанных данных, лучше использовать HandlerMethodArgumentResolver
Как обычно, все примеры всегда можно найти в нашем репозитории GitHub .