1. 前言
做web开发有一点很烦人就是要校验参数,基本上每个接口都要对参数进行校验,比如一些格式校验 非空校验都是必不可少的。如果参数比较少的话还是容易 处理的一但参数比较多了的话代码中就会出现大量的IF ELSE就比如下面这样:
这个例子只是校验了一下空参数。如果需要验证邮箱格式和手机号格式校验的话代码会更多,所以介绍一下validator通过注解的方式进行校验参数。
2. 什么是Validator
Bean Validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本 。在SpringBoot中已经集成在 starter-web中,所以无需在添加其他依赖。
但是还是建议加入 spring-boot-starter-validation 依赖,这样不会改变springboot的版本(且不用指定validation的版本号),从而不影响项目的其他业务功能。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3. 解释一下注解的作用
@Null 限制只能为null
@NotNull 限制必须不为null
@NotEmpty 只作用于字符串类型,字符串不为空,并且长度不为0
@NotBlank 只作用于字符串类型,字符串不为空,并且trim()后不为空串
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位
数不能超过fraction
@Future 限制必须是一个将来的日期
@Past 验证注解的元素值(日期类型)比当前时间早
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
注意:
@NotNull 适用于任何类型被注解的元素必须不能与NULL
@NotEmpty 适用于String Map或者数组不能为Null且长度必须大于0
@NotBlank 只能用于String上面 不能为null,调用trim()后,长度必须大于0
4. 在实体类加上要验证的字段。(我这里随便写下)
标注的地方就是用来分组校验的,下下面会解释。
package com.demo.demofordocker.pojo;
import com.demo.demofordocker.validate.group.LoginModel;
import com.demo.demofordocker.validate.group.SaveModel;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;
@ApiModel(value = "用户实体")
@Data
public class User {
@NotBlank(message = "{user.name.notBlank}",groups = LoginModel.class)
@NotBlank(message = "保存时,不能为空",groups = SaveModel.class)
private String name;
private Integer age;
private Date birthday;
@Valid //嵌套时需添加该注解
@NotNull(message = "对象登录不能为空" ,groups = LoginModel.class)
@NotNull(message = "对象保存不能为空" ,groups = SaveModel.class)
private Child child;
@Valid //嵌套时需添加该注解
@NotEmpty(message = "list登录不能为空",groups = LoginModel.class)
@NotEmpty(message = "list保存不能为空",groups = SaveModel.class)
private List<Child> childList;
}
通过groups的属性来分组,假设我在使用登录分组校验的时候,设定用户名不为空user.name.notBlank。而在保存分组设定保存时,不能为空。根据不通的请求参数校验可以不同。
@Valid用于嵌套分组
5. 在来解释下上面标注的分组接口。
LoginModel
import javax.validation.groups.Default;
public interface LoginModel extends Default {
}
必须继承默认的Defaut接口不然后抛出异常。
SaveModel
import javax.validation.groups.Default;
public interface SaveModel extends Default{
}
6. 在controller的接口上加上@Validated注解,参数就加上你需要根据那种规则来校验。
package com.demo.demofordocker.controller;
import com.demo.demofordocker.pojo.User;
import com.demo.demofordocker.validate.group.LoginModel;
import com.demo.demofordocker.validate.group.SaveModel;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Api(tags = "用户模块")
@RestController
public class UserController {
@ApiOperation("测试登录")
@PostMapping("getUser")
public User getUser( @RequestBody @Validated(LoginModel.class) User user) {
String name = user.getName();
System.out.println(name);
return user;
}
@ApiOperation("测试保存")
@PostMapping("getUser2")
public User getUser2( @RequestBody @Validated(SaveModel.class) User user) {
String name = user.getName();
System.out.println(name);
return user;
}
}
运行后只能在控制台显示错误的结果,新的问题又来了怎么把错误的结果通过自己的result类返回给前端。这就需要对错误全局捕捉了。
7. 写一个对Response换回结果的处理。
在类上方添加@RestControllerAdvice注解然后添加以下代码:返回message中指定的错误提示信息可以使用@ControllerAdvice或者@RestControllerAdvice结合@ExceptionHandle来实现:
@RestControllerAdvice
@Slf4j
public class ParameterCalibration {
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleMethodArgumentNotValidException(Exception exception) {
StringBuilder errorInfo = new StringBuilder();
BindingResult bindingResult=null;
if(exception instanceof MethodArgumentNotValidException){
bindingResult= ((MethodArgumentNotValidException)exception).getBindingResult();
}
if(exception instanceof BindException){
bindingResult= ((BindException)exception).getBindingResult();
}
for(int i = 0; i < bindingResult.getFieldErrors().size(); i++){
if(i > 0){
errorInfo.append(",");
}
FieldError fieldError = bindingResult.getFieldErrors().get(i);
errorInfo.append(fieldError.getField()).append(" :").append(fieldError.getDefaultMessage());
}
log.error(errorInfo.toString());
//这里返回自己的Result的结果类。
return Result.validateFailed(errorInfo.toString());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public Result handleDefaultException(Exception exception) {
log.error(exception.toString());
//这里返回自己的Result的结果类。
return Result.validateFailed("服务器错误",exception);
}
8. 校验单个参数
@PostMapping("/get")
public ReturnVO getUserInfo(@RequestParam("userId") @NotNull(message = "用户ID不能为空") String userId){
return new ReturnVO().success();
}
然后在Controller类上面增加@Validated注解,注意不是增加在参数前面。
9. @validated和@valid不同点
在spring项目中,@validated和@valid功能很类似,都可以在controller层开启数据校验功能。
但是@validated和@valid又不尽相同。有以下不同点:
1:分组
2:注解地方,@Valid可以注解在成员属性(字段)上,但是@Validated不行
3:由于第2点的不同,将导致@Validated不能做嵌套校验
4:@valid只能用在controller。@Validated可以用在其他被spring管理的类上。
对于第4点的不同,体现了@validated注解其实又更实用的功能。那就是@validated可以用在普通bean的方法校验上。
11. @validated的使用注意点
1:@validated和@valid都可以用在controller层的参数前面,但这只能在controller层生效。
2:@validated如果要开启方法验证。注解应该打在类上,而不是方法参数上。
3:方法验证模式下,被jsr303标准的注解修饰的可以是方法参数也可以是返回值,类似如下
public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
4:@validated不支持嵌套验证。所以jsr303标准的注解修饰的对象只能基本类型和包装类型。其他类型只能做到检测是否为空,
对于对象里面的jsr303标准的注解修饰的属性,不支持验证。
12. validation与 springboot 结合
- bean 中添加标签
部分代码: 标签需要加在属性上,@NotBlank 标签含义文章末尾有解释
public class User {
private Integer id;
@NotBlank(message = "{user.name.notBlank}")
private String name;
private String username;
}
- Controller中开启验证
在Controller 中 请求参数上添加@Validated 标签开启验证
@RequestMapping(method = RequestMethod.POST)
public User create(@RequestBody @Validated User user) {
return userService.create(user);
}
- resource 下新建错误信息配置文件
注:此处也可不在配置文件中配置,直接在验证的massage中写。
在resource 目录下新建提示信息配置文件“ValidationMessages.properties“。
注意:名字必须为“ValidationMessages.properties“ 因为SpringBoot自动读取classpath中的ValidationMessages.properties里的错误信息
ValidationMessages.properties 文件的编码为ASCII。数据类型为 key value 。key“user.name.notBlank“为第一步 bean的标签,大括号里面对应message的值,value 为提示信息 ,但是是ASCII 。(内容为“名字不能为空“)此处可以利用ASCII在线工具转换生成