1. 前言

做web开发有一点很烦人就是要校验参数,基本上每个接口都要对参数进行校验,比如一些格式校验 非空校验都是必不可少的。如果参数比较少的话还是容易 处理的一但参数比较多了的话代码中就会出现大量的IF ELSE就比如下面这样:

springboot 检测非法字符 springboot参数非空校验注解_java


这个例子只是校验了一下空参数。如果需要验证邮箱格式和手机号格式校验的话代码会更多,所以介绍一下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. 在实体类加上要验证的字段。(我这里随便写下)

springboot 检测非法字符 springboot参数非空校验注解_springboot 检测非法字符_02


标注的地方就是用来分组校验的,下下面会解释。

springboot 检测非法字符 springboot参数非空校验注解_spring_03

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注解,参数就加上你需要根据那种规则来校验。

springboot 检测非法字符 springboot参数非空校验注解_springboot 检测非法字符_04

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);
  }

springboot 检测非法字符 springboot参数非空校验注解_java_05

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 结合

  1. bean 中添加标签
    部分代码: 标签需要加在属性上,@NotBlank 标签含义文章末尾有解释
public class User {
	private Integer id;
 	@NotBlank(message = "{user.name.notBlank}")	
 	private String name;
 	private String username;
}
  1. Controller中开启验证
    在Controller 中 请求参数上添加@Validated 标签开启验证
@RequestMapping(method = RequestMethod.POST)
 
public User create(@RequestBody @Validated User user) {
	return userService.create(user);
}
  1. 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在线工具转换生成

springboot 检测非法字符 springboot参数非空校验注解_java_06