Spring的参数校验会用到两个库:validation-api,hibernate-validator validation-api是一套标准,hibernate-validator实现了此标准 JSR-303 是Java EE 6 中的一项子规范,叫做BeanValidation,官方参考实现是hibernate-validator。 (JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator) 开始使用validator,引入validator的依赖,非web场景(这里开发场景使用web开发场景,所以我们不单独引入该依赖). 主要的校验器有两种 hibernate-validator spring-boot-starter-validation 这两种都是对JSR303的实现,而spring-boot-starter-validation 又是对 hibernate-validator的二次封装。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
  <version>2.1.9.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

常规参数校验 Spring 参数校验作为 spring-context 模块的一部分存在,Validator 是 Spring 参数校验的核心接口,我们先看下这个接口。接口中只存在两个方法,supports 方法用于校验前确认是否支持给定的类型,validate方法用于参数校验,比较奇怪的是校验结果使用参数中的 Errors 存储及获取。

public interface Validator {

	// 当前验证器是否支持给定的类型
	boolean supports(Class<?> clazz);
	
	// 校验给定的 target,提供的 errors 对象用于存储和获取错误信息
	void validate(Object target, Errors errors);

}

@Data
public class LoginDTO {

    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    private String password;
}

public class App {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);

        Validator validator = context.getBean(LocalValidatorFactoryBean.class);
        LoginDTO loginDTO = new LoginDTO();
        Errors errors = new BeanPropertyBindingResult(loginDTO, "login");
        validator.validate(loginDTO, errors);
        errors.getAllErrors().forEach(System.out::println);

        context.close();
    }

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        return new LocalValidatorFactoryBean();
    }

}

常用参数校验

对于简单类型参数(非Bean),直接在参数前,使用注解添加约束规则。注解如下所示:

@AssertTrue / @AssertFalse

验证适用字段:boolean

注解说明:验证值是否为true / false

@DecimalMax / @DecimalMin

验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long

注解说明:验证值是否小于或者等于指定的小数值,要注意小数存在精度问题

@Digits

验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long

注解说明:验证值的数字构成是否合法

属性说明:integer:指定整数部分的数字的位数。fraction: 指定小数部分的数字的位数。

@Future / @Past

验证适用字段:Date,Calendar

注解说明:验证值是否在当前时间之后 / 之前

属性说明:公共

@Max / @Min

验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long

注解说明:验证值是否小于或者等于指定的整数值

属性说明:公共

注意事项:建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单提交的值为“”时无法转换为int

@NotNull / @Null

验证适用字段:引用数据类型

注解说明:验证值是否为非空 / 空

属性说明:公共

@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.

@NotEmpty 检查约束元素是否为Null或者是EMPTY.

@NotBlank 与 @NotEmpty 的区别:空格(" ")对于 NotEmpty 是合法的,而 NotBlank 会抛出校验异常

@Pattern

验证适用字段:String

注解说明:验证值是否配备正则表达式

属性说明:regexp:正则表达式flags: 指定Pattern.Flag 的数组,表示正则表达式的相关选项。

@Size

验证适用字段:String,Collection,Map,数组

注解说明:验证值是否满足长度要求

属性说明:max:指定最大长度,min:指定最小长度。

@Length(min=, max=):专门应用于String类型

@Valid

验证适用字段:递归的对关联对象进行校验

注解说明:如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验(是否进行递归验证)

属性说明:无

@Range(min=, max=) 被指定的元素必须在合适的范围内

@CreditCardNumber信用卡验证

@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。

@URL(protocol=,host=, port=,regexp=, flags=)

优雅的使用方式

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
 
/**
 * @Author: wangxia
 * @Date: 2021/10/20 16:30
 */
public class TestPerson {
 
    @NotEmpty(message = "用户名不能为空")
    private String username;
 
    @Min(value = 0,message = "年龄不能小于0岁")
    @Max(value =150,message = "年龄不能大于150岁")
    private int age;
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}

## 校验
@RequestMapping("/")
@RestController
public class TestValidatController {
 
    @PostMapping("/testValid")
    public String testValid(@Validated @RequestBody TestPerson testPerson){
        return "测试成功";
    }
 
}

## 捕获异常
@ControllerAdvice
@ResponseBody 
public class MethodArgumentNotValidHandel {
 
 
    @ExceptionHandler(value=MethodArgumentNotValidException.class)
    public JSONObject MethodArgumentNotValidHandler(HttpServletRequest request,
                                                    MethodArgumentNotValidException exception) throws Exception
    {
        JSONObject result=new JSONObject();
        result.put("code","fail");
        JSONObject errorMsg=new JSONObject();
        for (FieldError error : exception.getBindingResult().getFieldErrors()) {
            errorMsg.put(error.getField(),error.getDefaultMessage());
        }
        result.put("msg",errorMsg);
        return result;
    }
 
}


自定义验证注解

## 注解
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UserNameConstraintValidator.class)
@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface UserName {
    String message() default "username is invalid";
    Class<?>[] groups() default {};
    int min();
    Class<? extends Payload>[] payload() default { };
}
## 校验器
public class UserNameConstraintValidator implements ConstraintValidator<UserName, String> {

    private Integer min = 0;

    @Override
    public void initialize(UserName constraintAnnotation) {
       min = constraintAnnotation.min();
    }

    @Override
    public boolean isValid(String o, ConstraintValidatorContext constraintValidatorContext) {
        return StringUtils.hasText(o) && o.length()>min ;
    }
}

spring总的全局异常参数处理

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理标注了 @Validated 的类的方法调用参数校验失败导致的异常
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<?> handleConstraintViolationException(ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("|"));
        return Result.fail(message);
    }

    // 处理表单类型请求普通参数缺失导致的异常
    @ExceptionHandler(ServletRequestBindingException.class)
    public Result<?> handleServletRequestBindingException(ServletRequestBindingException e) {
        String message = e.getMessage();
        return Result.fail(message);
    }

    // 处理表单类型请求的复杂参数校验失败导致的异常
    @ExceptionHandler(BindException.class)
    public Result<?> handleBindException(BindException e) {
        String message = e.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining("|"));
        return Result.fail(message);
    }

    // 处理 application/json 类型请求的参数校验失败导致的异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining("|"));
        return Result.fail(message);
    }

}