📜 ⬆️ ⬇️

Validation of generic parameters in Spring controllers

image
We all often write simple methods in controllers working through numeric identifiers.

@RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET) @ResponseBody public Entity get(@PathVariable(value = "entityId") Integer entityId) { //    ID } 

The incoming ID must be validated. For example, not all users have access to all IDs.

It is understood that using a UUID is safer and more reliable. But it must be created, stored, indexed and so on. For all entities, doing this is long and difficult. In many cases, it is easier to work with ordinary numeric IDs.
')
Validation can be done in a simple way:

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

Plus, in this decision, only one. Simple and fast.
Everything else is bad. Duplication of the code, validation is not compatible with the validation of objects, separate processing is needed in each method.

I want to do this:

  @RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET) @ResponseBody public Entity get(@Validated @PathVariable(value = "entityId") Integer entityId) { //    ID } 

This simple and logical option does not work. The validator is simply not called. Validation of PathVariable Spring is not supported.

For this option to work, you must turn PathVariable into a ModelAttribute:

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

And get an error when accessing the controller. Wrappers generic types have no default constructor without parameters and no setter. It does this by using Optional. He has both a default constructor and a setter that accepts ordinary inty.

Turn Integer into Optional:

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

And accordingly the controller method itself and the validator declaration:

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

The validator class is absolutely normal:

 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"); } } } 

A full working example can be found here .

Source: https://habr.com/ru/post/425001/


All Articles