Валидация generic параметров в Spring контроллерах

от автора

image
Все мы часто пишем простые методы в контроллерах работающие через числовые идентификаторы.

    @RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)     @ResponseBody     public Entity get(@PathVariable(value = "entityId") Integer entityId) {         //возврат значения сущности по ID     }

Пришедший ID надо валидировать.Например, не у всех пользователей есть доступ ко всем ID.

Понято что использовать UUID безопаснее и надежнее. Но его надо создавать, хранить, индексировать итд. Для всех сущностей это делать долго и сложно. Во многих случаях проще работать по обычным числовым ID.

Валидацию можно сделать по простому:

    @RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)     @ResponseBody     public Entity get(@PathVariable(value = "entityId") Integer entityId) {         if(!@dao.validate(entityId))              return some_error;          //возврат значения сущности по ID     }

Плюс в таком решении только один. Просто и быстро.
Все остальное плохо. Дублирование кода, валидация не совместима с валидацией объектов, нужна отдельная обработка в каждом методе.

Хочется сделать так:

    @RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)     @ResponseBody     public Entity get(@Validated @PathVariable(value = "entityId") Integer entityId) {         //возврат значения сущности по ID     }

Этот простой и логичный вариант не работает. Валидатор просто не вызывается. Валидация PathVariable Спрингом не поддерживается.

Для работоспособности этого варианта надо превратить PathVariable в ModelAttribute:

     @ModelAttribute     private Integer integerAsModelAttribute(@PathVariable("entityId") Integer id) {         return id; }

И получить ошибку при обращении к контроллеру. У врапперов генерик типов нет дефолтного конструктора без параметров и нет сеттера. Обходится это с помощью использования Optional. У него есть и дефолтный конструктор и сеттер принимающий обычные инты.

Превращаем Integer в Optional:

     @ModelAttribute     private Integer integerAsModelAttribute(@PathVariable("entityId") Optional<Integer> id) {         return id.orElse(null);     }

И соответственно сам метод контроллера и объявление валидатора:

     @InitBinder({"entityId"})     protected void initCommissionIdBinder(WebDataBinder binder) {         binder.setValidator(validateEntityIdValidator);         binder.setBindEmptyMultipartFiles(false);     }      @RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)     @ResponseBody     public Entity get(@Validated @ModelAttribute(value = "entityId") Integer entityId) {        //Можно смело работать с ID. Оно уже валидировано.     }

Класс валидатора абсолютно обычный:

 public class ValidateIntegerValidator implements Validator {     @Override     public boolean supports(Class<?> aClass) {         return Integer.class.equals(aClass);     }      @Override     public void validate(Object o, Errors errors) {          if(o instanceof Integer) {             Integer integer = (Integer) o;              if(!dao.checkId(integer)) {                 errors.reject("-1", "ERROR");             }         } else {             errors.reject("-2","WRONG TYPE");         }      } }

Полный рабочий пример можно взять тут github.com/Okapist/IntegerValidationExample


ссылка на оригинал статьи https://habr.com/post/425001/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *