前言
在后端的接口开发过程,实际上每一个接口都或多或少有不同规则的参数校验,有一些是基础校验,如非空校验、长度校验、大小校验、格式校验;也有一些校验是业务校验,如学号不能重重复、手机号不能重复注册等;对于业务校验,是需要和数据库交互才能知道校验结果;对于参数的基础校验,是有一些共有特征可以抽象出来,可以做成一个通用模板(java就是一种面向对象的编程语言,还记得天天快要说烂问烂的面向对象的三大特性吗?)。基于实际场景的需要,java API中定义了一些Bean校验的规范标准(JSR303:validation-api),但是没有具体实现,不过hibernate validation和spring validation都提供了一些比较优秀的实现。如果在项目里,你还是像类似这样的方式来进行参数校验就太low了,活该加班到天亮(当然如果你所在公司目前仍然用统计代码量来考核你的工作,就算我没说,你可以继续使用这种方式)。
@PostMapping("/add")
public String add(Student student) {
if (null == student) {
throw new RuntimeException("学生不为空");
}
if ("".equals(student.getStuCode())) {
throw new RuntimeException("学号不能为空");
}
if ("".equals(student.getStuName())) {
throw new RuntimeException("学生姓名不能为空");
}
if (null == student.getTeacher()) {
throw new RuntimeException("学生的老师的不能为空");
}
if ("".equals(student.getTeacher().getTecName())) {
throw new RuntimeException("学生的老师的姓名不能为空");
}
if ("".equals(student.getTeacher().getSubject())) {
throw new RuntimeException("学生的老师的所授科目不为能空");
}
return "success";
}
依赖引入
分享的这篇文章里的校验参数注解使用方法,我是在一个springboot项目里亲自重新测试验证过的,springboot的版本是2.3.9.RELEASE,另外也引入了关于参数校验的starter包,这样就不用额外去引关于参数校验的其他包了;
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-validation
2.3.9.RELEASE
参数形式
在java项目中,前端请求后端的接口中,常用的请求类型主要是post和get。
- 在POST请求中,通常使用requestBody传递参数,即前端以json报文的格式传递到后端controller层,spring会把json报文自动映射到@RequestBody修饰的形参实例;
- 在GET请求中,通常使用requestParam/PathVariable传递参数,其中requestParam是指前端以key-value的形式把参数传递到后端,spring会把参数自动映射到@RequestParam修饰的形参数实例对象(@RequestParam可以,也可以没有,只要参数key与controller层方法内形参类型的属性名称可以对应的上);@PathVariable是指spring可以将请求URL中占位符参数绑定到controller层方法的形参上;
常用到的约束注解
@Valid |
被注释的元素是一个对象,需要检查此对象的所有字段值 |
@Null |
被注释的元素必须为 null |
@NotNull |
被注释的元素必须不为 null |
@AssertTrue |
被注释的元素必须为 true |
@AssertFalse |
被注释的元素必须为 false |
@Min(value) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) |
被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) |
被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past |
被注释的元素必须是一个过去的日期 |
@Future |
被注释的元素必须是一个将来的日期 |
@Pattern(value) |
被注释的元素必须符合指定的正则表达式 |
Hibernate Validator 附加的 constraint
注解 |
作用 |
|
被注释的元素必须是电子邮箱地址 |
@Length(min=, max=) |
被注释的字符串的大小必须在指定的范围内 |
@NotEmpty |
被注释的字符串的必须非空 |
@Range(min=, max=) |
被注释的元素必须在合适的范围内 |
@NotBlank |
被注释的字符串的必须非空 |
@URL(protocol=, host=, port=, regexp=, flags=) |
被注释的字符串必须是一个有效的url |
@CreditCardNumber |
被注释的字符串必须通过Luhn校验算法, 银行卡,信用卡等号码一般都用Luhn 计算合法性 |
@ScriptAssert (lang=, script=, alias=) |
要有Java Scripting API 即JSR 223 ("Scripting for the JavaTM Platform")的实现 |
@SafeHtml (whitelistType=, additionalTags=) |
classpath中要有jsoup包 |
参数基础校验
参数的基础校验,通常是指的非空、长度、最大值、最小值、格式(数字、邮箱、正则)等这些场景的校验。
@RequestBody参数
1.在controller层的方法的形参数前面加一个@Valid或@Validated的注解;
2.在用@RequestBody修饰的类的属性上加上约束注解,如@NotNull、@Length、@NotBlank;
3.@RequestBody参数在触发校验规则时,会抛出MethodArgumentNotValidException,这里使用统一的异常处理机制来处理异常;
总结:第1步的valid的作用就是一个标记,标明这个参数需要进行校验;第2步的约束注解的上注明校验的规则;第3步的统一校验机制是前后台请求后台接口时,如果校验参数的校验规则后会抛出异常,异常附带有约束注解上的提示信息,那么通过异常统一处理机制就可以统一处理异常信息,并以合适的方式返回给前台(所谓合适的方式是指异常信息的格式可以自行制定)。
@PostMapping("/add")
public Student add( @Valid@RequestBody Student student){
System.out.println(student.getStuName());
return student;
}
@Data
public class Student {
@NotNull(message = "学号不能为空")
@Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
private String stuCode;
@NotNull(message = "姓名不能为空")
@Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)")
private String stuName;
}
@RequestParam参数/@PathVariable参数
1.在controller层的控制类上添加@Validated注解;
2.在controller层方法的校验参数上添加约束注解,如@NotNull、@Pattern;
3.@RequestParam参数/@PathVariable参数在触发校验规则时,会抛出ConstraintViolationException类型的异常,所以在统一异常处理机制中添加对这种类型异常的处理机制;
@RestController
@RequestMapping("/student")
@Validated
public class StudentController {
@GetMapping("/{sex}/info")
public String getBySex(@PathVariable("sex") @Pattern(regexp = "boy||girl",message = "学生性别只能是boy或girl") String sex) {
System.out.println("学生性别:" + sex);
return "success";
}
@GetMapping("/getOne")
public String getOne(@NotNull(message = "学生姓名不能为空") String stuName, @NotNull(message = "学生学号不能为空") String stuCode) {
System.out.println("stuName:" + stuName + ",stuCode:" + stuCode);
return "success";
}
}
异常统一处理
@RestControllerAdvice
public class CommonExceptionHandler {
/**
* 用于捕获@RequestBody类型参数触发校验规则抛出的异常
*
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public String handleValidException(MethodArgumentNotValidException e) {
StringBuilder sb = new StringBuilder();
List allErrors = e.getBindingResult().getAllErrors();
if (!CollectionUtils.isEmpty(allErrors)) {
for (ObjectError error : allErrors) {
sb.append(error.getDefaultMessage()).append(";");
}
}
return sb.toString();
}
/**
* 用于捕获@RequestParam/@PathVariable参数触发校验规则抛出的异常
*
* @param e
* @return
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public String handleConstraintViolationException(ConstraintViolationException e) {
StringBuilder sb = new StringBuilder();
Set