<div style="font-size:16px;">
<p>之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation。本文会详细介绍Spring Validation各种场景下的最佳实践及其实现原理,死磕到底!</p>
<p>项目源码:spring-validation</p>
<p>简单使用</p>
<p>Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。接下来,我们以spring-boot项目为例,介绍Spring Validation的使用。</p>
<p>引入依赖</p>
<p>如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖:</p>
<p>org.hibernate</p>
<p>hibernate-validator</p>
<p>6.0.1.Final</p>
<p>对于web服务来说,为防止非法参数对业务造成影响,在Controller层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:POST、PUT请求,使用requestBody传递参数;</p>
<p>GET请求,使用requestParam/PathVariable传递参数。</p>
<p>下面我们简单介绍下requestBody和requestParam/PathVariable的参数校验实战!</p>
<p>requestBody参数校验</p>
<p>POST、PUT请求一般会使用requestBody传递参数,这种情况下,后端使用DTO对象进行接收。只要给DTO对象加上@Validated注解就能实现自动参数校验。比如,有一个保存User的接口,要求userName长度是2-10,account和password字段长度是6-20。如果校验失败,会抛出MethodArgumentNotValidException异常,Spring默认会将其转为400(Bad Request)请求。DTO表示数据传输对象(Data Transfer Object),用于服务器和客户端之间交互传输使用的。在spring-web项目中可以表示用于接收请求参数的Bean对象。在DTO字段上声明约束注解@Data</p>
<p>public class UserDTO {<!-- --></p>
<p>private Long userId;</p>
<p>@NotNull</p>
<p>@Length(min = 2, max = 10)</p>
<p>private String userName;</p>
<p>@NotNull</p>
<p>@Length(min = 6, max = 20)</p>
<p>private String account;</p>
<p>@NotNull</p>
<p>@Length(min = 6, max = 20)</p>
<p>private String password;</p>
<p>}在方法参数上声明校验注解@PostMapping("/save")</p>
<p>public Result saveUser(@RequestBody @Validated UserDTO userDTO) {<!-- --></p>
<p>// 校验通过,才会执行业务逻辑处理</p>
<p>return Result.ok();</p>
<p>}这种情况下,使用@Valid和@Validated都可以。</p>
<p>requestParam/PathVariable参数校验</p>
<p>GET请求一般会使用requestParam/PathVariable传参。如果参数比较多(比如超过6个),还是推荐使用DTO对象接收。否则,推荐将一个个参数平铺到方法入参中。在这种情况下,必须在Controller类上标注@Validated注解,并在入参上声明约束注解(如@Min等)。如果校验失败,会抛出ConstraintViolationException异常。代码示例如下:@RequestMapping("/api/user")</p>
<p>@RestController</p>
<p>@Validated</p>
<p>public class UserController {<!-- --></p>
<p>// 路径变量</p>
<p>@GetMapping("{userId}")</p>
<p>public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {<!-- --></p>
<p>// 校验通过,才会执行业务逻辑处理</p>
<p>UserDTO userDTO = new UserDTO();</p>
<p>userDTO.setUserId(userId);</p>
<p>userDTO.setAccount("11111111111111111");</p>
<p>userDTO.setUserName("xixi");</p>
<p>userDTO.setAccount("11111111111111111");</p>
<p>return Result.ok(userDTO);</p>
<p>}</p>
<p>// 查询参数</p>
<p>@GetMapping("getByAccount")</p>
<p>public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) {<!-- --></p>
<p>// 校验通过,才会执行业务逻辑处理</p>
<p>UserDTO userDTO = new UserDTO();</p>
<p>userDTO.setUserId(10000000000000003L);</p>
<p>userDTO.setAccount(account);</p>
<p>userDTO.setUserName("xixi");</p>
<p>userDTO.setAccount("11111111111111111");</p>
<p>return Result.ok(userDTO);</p>
<p>}</p>
<p>}</p>
<p>统一异常处理</p>
<p>前面说过,如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。比如我们系统要求无论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。@RestControllerAdvice</p>
<p>public class CommonExceptionHandler {<!-- --></p>
<p>@ExceptionHandler({MethodArgumentNotValidException.class})</p>
<p>@ResponseStatus(HttpStatus.OK)</p>
<p>@ResponseBody</p>
<p>public Result handleMethodArgumentNotValidException(Metho |
|