SpringBoot2.7+借助Validator帮助进行校验
Validator
实际上Validator在前端中后端中都有,本次介绍后端中我们常使用Validator进行数据校验
常用注解
注解 | 说明 |
---|---|
@AssertTrue | 用于boolean字段,该字段只能为true |
@AssertFalse | 用于boolean字段,该字段只能为false |
@CreditCardNumber | 对信用卡号进行一个大致的验证 |
DecimalMax | 只能小于或等于该值 |
@DecimalMin | 只能大于或等于该值 |
检查是否是一个有效的email地址 | |
@Future | 检查该字段的日期是否是属于将来的日期 |
@Length(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
@Max | 该字段的值只能小于或等于该值 |
@Min | 该字段的值只能大于或等于该值 |
@NotNull | 不能为null |
@NotBlank | 不能为空,检查时会将空格忽略 |
@NotEmpty | 不能为空,这里的空是指空字符串 |
@Pattern(regex=) | 被注释的元素必须符合指定的正则表达式 |
@URL(protocol=,host,port) | 检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件 |
QuickStart
1.添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.编写Controller加上@Validated
@RestController
//开启校验
@Validated
public class TestController {}
3.简单校验(校验传递参数,非对象类型)
使用简单校验我们通常用于校验一些简单的数据传输的方法,比如get方法和delete方法,通常传输的是非表单类数据,此类数据不会解析成对象类型所以可以直接使用简单校验
基础请求
如:
@RequestMapping("/test5")
public String getId4(@NotNull(message = "id不能为空") Integer id) {
System.out.println(id);
return "over4!";
}
这是一个最简单的请求,我们在参数前加上了@NotNull
注解表示参数不能为空,接下来我们进行请求,结果如下
我们可以看到页面为500的错误并且,后端报了我们设置在@NotNull
注解中的错误
Get请求
接下来我们测试Get请求
@GetMapping("/test4")
public String getId3(@NotNull(message = "id不能为空") Integer id) {
System.out.println(id);
return "over3!";
}
经过测试,你将看到和刚刚基础的@RequestMapping
相同的结果
借助@RequestParam
注解引导接收的Get请求
这次我们使用@RequestParam("id")
进行接收
@GetMapping("/test3")
public String getId2(@RequestParam("id") @NotNull(message = "id不能为空") Integer id) {
System.out.println(id);
return "over2!";
}
这次的结果有些不同了
这次并不是500,而是400说明请求是一个不好的请求策略即Bad request
,这是因为使用@RequestParam
会将请求参数中的id与变量进行绑定,若你的请求并没有id则认为你的请求有问题,是一个Bad Request
,而且后端也不会认为后端接收有问题,因为这说明是前端的问题!你自然在后端找不到任何报错,相应的也不会进行校验数据这步,所以根本不会报出@NotNull
中的错误消息
使用RestFul风格进行Get请求
我们现在在开发中常会使用RestFul风格的请求,所以我们也来尝试一下
@GetMapping("/test2/{id}")
public String getId(@PathVariable("id") @NotNull(message = "id不能为空") Integer id) {
System.out.println(id);
return "over!";
}
请求结果如下:
没错你会直接看到一个404,这是什么原因呢?
实际上RestFul风格是将请求参数看作静态资源进行请求,在处理的时候会获取{}
中的参数再进行转化,所以不带有直接行为的请求当然找不到资源
这里的直接行为指的是直接把参数的数值绑定上去进行请求
所以后端也就认为你请求了一个我没有的资源我就无法给你提供处理,自然也不会进行校验
4.Post请求的校验
如下我们编写一个User类
User
package com.example.validatortest;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotEmpty;
@Data
public class User {
@NotEmpty(message = "用户ID不能为空")
@Length(max = 15,min = 6,message = "用户名长度为6~15位")
private String username;
@NotEmpty(message = "用户email不能为空")
@Email(message = "邮箱错误请检查")
private String email;
@Max(value = 18,message = "超过18岁无法使用")
private int age;
}
添加Post请求
@PostMapping("/test")
public String test( @Validated @RequestBody User user) {
System.out.println(user);
return "ok!";
}
测试
首先测试正常请求
我们使用ApiFox进行发送请求
如下正常情况下通过
接下来我们测试请求参数错误情况
此时我们将用户名和年龄进行修改如下
请求后返回的结果是400说明请求是个Bad Request
我们看控制台发现
控制台没有报Error而是报Warn
对Warn进行查看发现确实有输出我们设置校验错误的信息,说明是进行了校验的
分组校验
我们可以定义两个内部接口进行指定
示例如下
1.指定校验规则的组
@NotEmpty(groups = {Define.class})
2.编写内部接口
只要写个空实现就可以,用于区别
private interface Define{
}
3.在@Validated
中指明组
@Validated(value={Define.class})
这样标志位Define.class的就会进行校验,完成分组
统一异常处理
我这里直接给大家写好了,拿了直接用就行
package com.example.validatortest;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
public class CHandler {
@ExceptionHandler({BindException.class, ConstraintViolationException.class})
public String handler(HttpServletRequest request, Exception ex){
String msg="";
if (ex instanceof ConstraintViolationException){
ConstraintViolationException ce = (ConstraintViolationException) ex;
ConstraintViolation<?> next = ce.getConstraintViolations().iterator().next();
msg = next.getMessage();
}else if(ex instanceof BindException){
BindException be = (BindException) ex;
List<FieldError> fieldErrors = be.getBindingResult().getFieldErrors();
for (FieldError fieldError : fieldErrors) {
msg+=fieldError.getDefaultMessage()+"||||";
}
}
return msg;
}
}
经过统一异常处理后测试POST
我们再次重新测试前面的POST请求发现:
返回结果已经完成了自定义的显示
当然再测试前面的GET请求也是一样
@Valid
和@Validated
区别
@Validated
支持方法参数自动校验@Validated
支持分组校验
校验拦截(我这样称呼)
我们需要实现校验发现第一个错误就直接停止,后续不需要进行校验,直接返回错误信息,这样可以大幅度缩减校验时间
其实我们称为快速失败返回模式
以下代码直接开启了全局的快速失败返回模式,使用了@Configuration
注解
package com.example.validatortest;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@Configuration
public class ValidateConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validFactory = Validation.byProvider(HibernateValidator.class)
.configure()
//设置快速失败返回模式
.addProperty("hibernate.validator.fail_fast","true")
.buildValidatorFactory();
return validFactory.getValidator();
}
//开启快速返回,参数校验有异常直接抛出不进入controller中,使用全局异常拦截
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(){
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
postProcessor.setValidator(validator());
return postProcessor;
}
}
使用自定义注解方式开启
我们可以采用自定义注解方式控制快速失败返回模式是否开启
1.去除前面的@Configuration
注解
package com.example.validatortest;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class ValidateConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validFactory = Validation.byProvider(HibernateValidator.class)
.configure()
//设置快速失败返回模式
.addProperty("hibernate.validator.fail_fast","true")
.buildValidatorFactory();
return validFactory.getValidator();
}
//开启快速返回,参数校验有异常直接抛出不进入controller中,使用全局异常拦截
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(){
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
postProcessor.setValidator(validator());
return postProcessor;
}
}
2.编写注解
目的是使用该注解可以直接配置ValidateConfiguration
这个类,相当于完成@Configuration
注解的事情
package com.example.validatortest;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ValidateConfiguration.class)
public @interface EnableQuickCallBack {
}
3.在启动类上加上自定义的注解开启
4.测试
加上之前
校验了所有的
加上之后
解决RestFul风格存在的校验问题
前面我们使用RestFul风格的请求都是反馈为404表示无法请求到资源
我们现在进行解决
首先我们需要知道RestFul的请求常使用在对用户资源请求是绑定userId,或是请求某类资源时,比如博客的文章资源,由于资源很多,所以采用uuid或者雪花算法类进行设置,这样的请求携带的参数是很长的一个ID值,我们后端不能使用Integer或int接收或者返回而是使用String,以为前端常会因为精度错误的问题导致一些类的错误
解决
- 将后端接收的参数设置为String类型进行接收
- 与前端约定传递包含默认值例如:0,解释一下,前端通过AJAX或Axios进行传递必然需要携带参数,将参数设定一个默认值表示请求资源需要被直接拦截
- 实际无需担心这类无资源的问题
这里解释一下第三点,因为若前端需要向后端请求RestFul风格的请求绝大多数情况下前端已经拿到了后端传递给前端的资源列表,从资源列表中获取值进行前端,本身就一定带有值,而且这个值本来就是后端传给前端的,对于后端来说只要这个资源没有在更新给前端之前就删除,前端一定能够根据给出的ID请求到,所以根本无需担心,而且我们的Validator的主要作用还是帮助我们进行表单的校验,不要本末倒置