当前位置: 首页 > 后端技术 > Java

SpringBoot的参数验证之SpringValidation

时间:2023-04-01 21:21:26 Java

前言JavaAPI规范(JSR303)定义了Bean验证的标准validation-api,但没有提供实现。hibernatevalidation是对这个规范的实现,增加了@Email、@Length等验证注解。SpringValidation是对hibernatevalidation的二次封装,用于支持springmvc参数的自动校验。下面我们以spring-boot项目为例,介绍一下SpringValidation的使用。涵盖几乎所有你需要引入依赖的SpringBoot操作一:org.hibernatehibernate-validator6.2.3.Final方法二:(本文采用此方法)org.springframework.bootspring-boot-starter-validation这个实战部分article还需要在web服务中引入web依赖org.springframework.bootspring-boot-starter-web通常通过如下方式两种形式参数传递:POST和PUT请求,使用requestBody传递参数;GET请求,使用requestParam/PathVariable传递参数。requestBody参数验证POST和PUT请求一般使用requestBody来传递参数。在这种情况下,后端使用DTO对象来接收它们。只需在DTO对象上加上@Validated注解即可实现参数自动校验。比如有一个保存User的接口,要求userName的长度为2-10,account和password字段的长度为6-20。如果验证失败,会抛出MethodArgumentNotValidException,Spring默认会转为400(BadRequest)请求。DTO代表数据传输对象(DataTransferObject),用于服务端和客户端的交互传输。用于接收请求参数的Bean对象可以在spring-web项目中表示。在DTO字段上声明约束注释@NotNull@Length(min=2,max=10)私有字符串用户名;@NotNull@Length(min=6,max=20)私人字符串帐户;@NotNull@Length(min=6,max=20)私有字符串密码;publicLonggetUserId(){返回userId;}publicvoidsetUserId(LonguserId){this.userId=userId;}publicStringgetUserName(){返回用户名;}publicvoidsetUserName(StringuserName){this.userName=userName;}publicStringgetAccount(){返回账户;}publicvoidsetAccount(Stringaccount){this.account=account;}publicStringgetPassword(){返回密码;}publicvoidsetPassword(Stringpassword){this.password=password;}}在这种情况下,在方法参数上声明验证注解,@Valid和@Validated都可以。importjava.util.HashMap;importjava.util.Map;@RestControllerpublicclassUserController{@PostMapping("/save")publicMapsaveUser(@RequestBody@ValidatedUserDTOuserDTO){Mapres=newHashMap();res.put("代码","200");res.put("消息","确定");返回资源;}}requestParam/PathVariable参数校验GET请求一般使用requestParam/PathVariable传参。如果参数较多(比如超过6个),建议使用DTO对象接收。否则,建议将参数一个一个压平成方法输入参数。这种情况下,必须在Controller类上标注@Validated注解,在入参上声明约束注解(如@Min等)。如果验证失败,将抛出ConstraintViolationException。代码示例如下:@RestController@ValidatedpublicclassSelectController{@GetMapping("/{userId}")publicMapdetail(@PathVariable("userId")@Min(1000000000L)LonguserId){Mapres=newHashMap();res.put("代码","200");res.put("消息","确定");返回资源;}@GetMapping("/getByAccount")publicMapgetByAccount(@Length(min=6,max=20)@NotNullStringaccount){Mapres=newHashMap();res.put("代码","200");res.put("消息","确定");返回资源;}}统一的异常处理如果验证失败,将抛出MethodArgumentNotValidException或ConstraintViolationException。我们需要通过统一的异常处理返回更友好的错误报告。导入org.springframework.http.HttpStatus;导入org.springframework.validation.BindingResult;导入org.springframework.validation.FieldError;导入org.springframework.web.bind.MethodArgumentNotValidException;导入org.springframework.web.bind.annotation.ExceptionHandler;导入org.springframework.web.bind.annotation.ResponseStatus;导入org.springframework.web.bind.annotation.RestControllerAdvice;导入javax.validation.ConstraintViolationException;导入java.util.HashMap;导入java.util.Map;@RestControllerAdvicepublic类CommonExceptionHandler{@ExceptionHandler({MethodArgumentNotValidException.class})@ResponseStatus(HttpStatus.OK)publicMaphandleMethodArgumentNotValidException(MethodArgumentNotValidExceptionex){BindingResultbindingResult=ex.getBindingResult();StringBuildersb=newStringBuilder("校试失败:");对于(FieldErrorfieldError:bindingResult.getFieldErrors()){sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(",");}字符串消息=sb.toString();Maperr=newHashMap<>();err.put("代码","-200");err.put("消息",味精);返回错误;}@ExceptionHandler({ConstraintViolationException.class})@ResponseStatus(HttpStatus.OK)publicMaphandleConstraintViolationException(ConstraintViolationExceptionex){Maperr=newHashMap<>();err.put("代码","-200");err.put("消息",ex.getMessage());返回错误;}}高级使用分组验证在实际项目中,可能有多个方法需要使用同一个DTO类来接收参数,不同方法的验证规则很可能是不一样的。这时候单纯的给DTO类的字段加上约束注解是不能解决这个问题的。所以spring-validation支持分组验证的功能,就是专门用来解决这类问题的。还是上面的例子,比如保存User时,UserId可以为空,但是更新User时,UserId的值必须>=10000000000000000L;其他字段的验证规则在这两种情况下是相同的。此时使用组校验的代码示例如下:在约束注解groups上声明了适用的组信息publicclassUserDTO{@Min(value=10000000000000000L,groups=Update.class)privateLonguserId;@NotNull(groups={Save.class,Update.class})@Length(min=2,max=10,groups={Save.class,Update.class})privateStringuserName;@NotNull(groups={Save.class,Update.class})@Length(min=6,max=20,groups={Save.class,Update.class})privateStringaccount;@NotNull(groups={Save.class,Update.class})@Length(min=6,max=20,groups={Save.class,Update.class})privateStringpassword;publicinterfaceSave{}publicinterfaceUpdate{}}@Validated注解指定验证组@PostMapping("/save")publicMapsaveUser(@RequestBody@Validated(UserDTO.Save.class)UserDTOuserDTO){Mapres=newHashMap();res.put("代码","200");res.put("消息","确定");退役瓮资源;}@PostMapping("/update")publicMapupdateUser(@RequestBody@Validated(UserDTO.Update.class)UserDTOuserDTO){Mapres=newHashMap();res.put("代码","200");res.put("消息","确定");返回资源;}