Pre
SpringBoot - 优雅的实现【自定义参数校验】高级进阶
SpringBoot - 优雅的实现【参数分组校验】高级进阶
SpringBoot - 使用Assert校验让业务代码更简洁
概述
日常开发中,对入参进行参数校验是必不可少的一个环节。 而使用最多的就是Validator框架 。
Validator校验框架遵循了JSR-303 【Java Specification Requests】验证规范 。
这里我们探讨下,在boot项目中如何优雅的集成参数校验框架
参数校验三部曲
Step1 搞依赖
boot 2.3 以后版本的pom信息如下
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>
- spring boot-2.3之前的版本只需要引入 spring-boot-starter-web 即可 ,已经包含了
- spring boot-2.3及以后的版本,校验包是一个单独的starter,需要同时引入spring-boot-starter-validation
Step2 搞参数校验的实体类
package com.artisan.vo; import lombok.Data; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; /** * @author 小工匠 * @version 1.0 * @mark: show me the code , change the world */ @Data public class Artisan { private String id; @NotBlank(message = "名字为必填项") private String name; @Length(min = 8, max = 12, message = "password长度必须位于8到12之间") private String password; @Email(message = "请填写正确的邮箱地址") private String email; private String sex; @NotEmpty(message = "Code不能为空") private String code; }
常用的校验注解
注解 | 功能 |
@AssertFalse | 可以为null,如果不为null的话必须为false |
@AssertTrue | 可以为null,如果不为null的话必须为true |
@DecimalMax | 设置不能超过最大值 |
@DecimalMin | 设置不能超过最小值 |
@Digits | 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 |
@Future | 日期必须在当前日期的未来 |
@Past | 日期必须在当前日期的过去 |
@Max | 最大不得超过此最大值 |
@Min | 最大不得小于此最小值 |
@NotNull | 不能为null,可以是空 |
@Null | 必须为null |
@Pattern | 必须满足指定的正则表达式 |
@Size | 集合、数组、map等的size()值必须在指定范围内 |
必须是email格式 | |
@Length | 长度必须在指定范围内 |
@NotBlank | 字符串不能为null,字符串trim()后也不能等于“” |
@NotEmpty | 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“” |
@Range | 值必须在指定范围内 |
@URL | 必须是一个URL |
Step3 开始验证
注意看注释
package com.artisan.controller; import com.artisan.vo.Artisan; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.Email; /** * @author 小工匠 * @version 1.0 * @mark: show me the code , change the world */ @RestController @Slf4j @Validated @RequestMapping("/valid") public class ArtisanController { /** * 使用@RequestBody注解,用于接受前端发送的json数据 * * @param artisan * @return */ @PostMapping("/testJson") public String testJson(@Validated @RequestBody Artisan artisan) { log.info("InComing Param {}", artisan); return "testJson valid success"; } /** * 模拟表单提交 * * @param artisan * @return */ @PostMapping(value = "/testForm") public String testForm(@Validated Artisan artisan) { log.info("InComing Param is {}", artisan); return "testForm valid success"; } /** * 模拟单参数提交 * * @param email * @return */ @PostMapping(value = "/testParma") public String testParma(@Email String email) { log.info("InComing Param is {}", email); return "testParma valid success"; } }
- testJson使用 @RequestBody注解,用于接受前端发送的json数据
- testForm模拟表单提交
- testParma模拟单参数提交
当使用单参数校验时需要在Controller上加上@Validated注解,否则不生效
【测试第一个接收JSON的接口 】
可以看到抛出的异常为: org.springframework.web.bind.MethodArgumentNotValidException
【测试第二个接收表单的接口 】
可以看到抛出的异常为: org.springframework.validation.BindException
【测试第三个接收单参数的接口 】
可以看到抛出的异常为:javax.validation.ConstraintViolationException
存在的问题
且不说好不好看, 不管怎么样,现在是通过Validation框架实现了校验。 当然了,我们的追求肯定不是这样的,Validator校验框架返回的错误提示太臃肿了 ,格式啥的都不一样,很难搞哦, 怎么给前台返回????
使用 统一格式 + 全局异常Handler 优化
增加统一返回 和 全局异常Handler,单独拦截参数校验的三个异常:javax.validation.ConstraintViolationException
,org.springframework.validation.BindException
,org.springframework.web.bind.MethodArgumentNotValidException
/** * @param e * @return */ @ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class}) public ResponseEntity<ResponseData<String>> handleValidatedException(Exception e) { ResponseData<String> resp = null; if (e instanceof MethodArgumentNotValidException) { // BeanValidation exception MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e; resp = ResponseData.fail(HttpStatus.BAD_REQUEST.value(), ex.getBindingResult().getAllErrors().stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.joining("; ")) ); } else if (e instanceof ConstraintViolationException) { // BeanValidation GET simple param ConstraintViolationException ex = (ConstraintViolationException) e; resp = ResponseData.fail(HttpStatus.BAD_REQUEST.value(), ex.getConstraintViolations().stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining("; ")) ); } else if (e instanceof BindException) { // BeanValidation GET object param BindException ex = (BindException) e; resp = ResponseData.fail(HttpStatus.BAD_REQUEST.value(), ex.getAllErrors().stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.joining("; ")) ); } log.error("参数校验异常:{}", resp.getMessage()); return new ResponseEntity<>(resp, HttpStatus.BAD_REQUEST); }
重新测试
源码
https://github.com/yangshangwei/boot2