Java 代码简介之道 - Bean Validation - hibernate-validator - spring-boot-starter-validation

前言

Jakarta Bean Validation 官网

https://beanvalidation.org/

hibernate-validator 官网

https://hibernate.org/validator/

传统的参数校验

  1. UserInfo
package org.example.beans;

import java.time.LocalDateTime;

public class UserInfo {
    private long id;
    // name 要求不能是 null , "" , "   " :
    private String name;
    // age 要求是正整数,1 ~ 800 :
    private Integer age;
    // email 的格式要求是 ... :
    private String email;
    // phone 要符合中国大陆手机号码的格式:
    private String phone;
    // birthday 要求在当前日期之前:
    private LocalDateTime birthday;
    // personalPage 要求格式符合 URL 规范:
    private String personalPage;

    // Get 、Set 方法……
}
  1. Main
package org.example;

import org.example.beans.UserInfo;

public class Main {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("   ");
        test1(userInfo);
    }

    // 使用传统的方式来校验 bean :
    private static void test1(UserInfo userInfo) {
        String name = userInfo.getName();
        if (name == null || "".equals(name) || "".equals(name.trim())) {
            // 不符合校验规则,抛出异常:
            throw new RuntimeException("name 不符合校验规则");
        }
        // age 的校验:
        Integer age = userInfo.getAge();
        boolean ageValidate = age > 0 && age < 800;
        if (!ageValidate) {
            throw new RuntimeException("age 不符合校验规则,应在 (0, 800)");
        }
        // ......
    }
}

Java EE 规范

Java EE 规范的制定者:JCP 官网

https://jcp.org/en/home/index

非 Web 环境下使用校验

引入相关依赖

<!--        引入 hibernate-validator ,同时会传递引入 jakarta.validation-api :-->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>8.0.0.Final</version>
        </dependency>
<!--        校验不通过需要提示,无此依赖会报错; -->
<!--        el 规范的 tomcat 实现,用于解析 message 里面的 el 表达式:-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <version>10.1.9</version>
        </dependency>

validator 初体验

  1. 给 UserInfo Bean 添加校验注释
package org.example.beans;

import jakarta.validation.constraints.NotBlank;

import java.time.LocalDateTime;

public class UserInfo {
    private long id;
    // name 要求不能是 null , "" , "   " :
    @NotBlank
    private String name;
    // age 要求是正整数,1 ~ 800 :
    private Integer age;
    // email 的格式要求是 ... :
    private String email;
    // phone 要符合中国大陆手机号码的格式:
    private String phone;
    // birthday 要求在当前日期之前:
    private LocalDateTime birthday;
    // personalPage 要求格式符合 URL 规范:
    private String personalPage;

    // Get 、Set 方法……
}
  1. 定义 ValidateUtil
package org.example.utils;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.example.beans.UserInfo;

import java.util.List;
import java.util.Set;

// 线程安全:
public class ValidateUtil {
    private static final Validator validator;

    static {
        try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
            validator = validatorFactory.getValidator();
        }
    }

    public static List<String> valid(UserInfo userInfo) {
        // 如果被校验对象 userInfo 没有校验通过,则 Set 里面就有校验信息:
        Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo);
        return set.stream().map(v ->
                "属性:" + v.getPropertyPath() + " ,属性的值:" + v.getInvalidValue() + " ,校验不通过的提示信息:" + v.getMessage())
                .toList();
    }
}
  1. Main
package org.example;

import org.example.beans.UserInfo;
import org.example.utils.ValidateUtil;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        List<String> list = ValidateUtil.valid(userInfo);
        System.out.println(list);
    }
}
  1. 运行结果
[属性:name ,属性的值:null ,校验不通过的提示信息:不能为空]

validator 加载原理

org.hibernate.validator.internal.engine.ValidatorImpl
spi :service provider interface ,是 jdk 提供的一种服务发现机制。

常用的校验注解约束

JSR 提供的校验注解:

  1. Bean Validation 中内置的 constraint
@Null // 被注释的元素必须为 null
@NotNull // 被注释的元素必须不为 null
@NotEmpty // 被注释的集合(size > 0) 或字符串(!= null && != "")
@NotBlank // != null && != "" && != "   "
@AssertTrue // 被注释的元素必须为 true
@AssertFalse // 被注释的元素必须为 false
@Min(value) // 被注释的元素必须是一个数字,>=
@Max(value) // 被注释的元素必须是一个数字,<=
@Size (max=, min=) // 被注释的元素的大小必须在指定的范围内
@Digits(integer, fraction) // 被注释的元素必须是一个数字,且在 integer 上下浮动不超过 fraction
@Past 过去的日期
@PastOrPresent // 时间
@Futrue // 被注释的元素必须是一个将来的日期
@NegativeOrZero // <= 0
@Pattern(regex=, flag=) // 被注释的元素必须符合指定的正则表达式
@Email // 被注释的元素必须是电子邮箱地址
@DecimalMin(value) // 最小值 value
@DecimalMax(value) // 最大值 value
  1. Hibernate Validator 附加的 constraint
@Length(min=, max=) // 被注释的字符串的长度必须在指定的范围 min ~ max
@Range(min=, max=, message=) // 被注释的元素必须在指定的范围 [min, max]
@URL // 一个 URL
  1. 使用示例
    要看一个约束可以作用在哪些类型上,直接点进去看源码开头的注释就行了。
package org.example.beans;

import jakarta.validation.constraints.*;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;

import java.time.LocalDateTime;

public class UserInfo {
    private long id;

    // name 要求不能是 null , "" , "   " :
    // @NotNull // 只校验不为 null 的
    // @NotEmpty // != null && != "" ,可以为 "   "
    @NotBlank
    private String name;

    // age 要求是正整数,[1, 800] :
    @NotNull
    // @Min(1) @Max(800) // 闭区间,只有 != null 的时候才生效
    @Range(min = 1, max = 800)
    private Integer age;

    // 要求符合 email 的格式:
    @NotNull
    @Email
    private String email;

    // phone 要符合中国大陆手机号码的格式:
    @Pattern(regexp = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$")
    private String phone;

    // birthday 要求在当前日期之前:
    @NotNull
    @Past
    private LocalDateTime birthday;

    // personalPage 要求格式符合 URL 规范:
    @URL
    private String personalPage;

    // Get 、Set 方法……
}

约束和校验类的绑定原理

  • 约束注解与约束校验器的绑定:org.hibernate.validator.internal.metadata.core.ConstraintHelper
  • XXXValidator 用来校验 XXX 约束注解,如 ....NotBlankValidator
  • 注意:一个约束注解可能对应多个 validator

自定义消息和消息模版

示例:

  1. 更改 UserInfo
public class UserInfo {
    // ...

    // age 要求是正整数,[1, 800] :
    @NotNull
    // @Min(1) @Max(800) // 闭区间,只有 != null 的时候才生效
    // @Range(min = 1, max = 800)
    @Min(value = 18, message = "年龄小于 {value} 岁,禁止进入!") // 此处使用了 el 表达式,注意,{value} 不能写成 { value } ,否则失效
    private Integer age;

    // ...
}
  1. 更改 ValidationUtil
public class ValidateUtil {
    // ...

    public static List<String> valid(UserInfo userInfo) {
        // 如果被校验对象 userInfo 没有校验通过,则 Set 里面就有校验信息:
        Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo);
        return set.stream().map(v ->
                "属性:" + v.getPropertyPath() + " ,属性的值:" + v.getInvalidValue() + " ,校验不通过的提示信息:" + v.getMessage() + " ,信息模版:" + v.getMessageTemplate()) // 消息模版就是 el 表达式还没有被替换成值之前的原始字符串
                .toList();
    }
}
  1. 更改 Main
public class Main {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("bateman");
        userInfo.setAge(17);
        userInfo.setEmail("2530788542@qq.com");
        userInfo.setPhone("13800138000");
        userInfo.setBirthday(LocalDateTime.now().minusDays(1));
        userInfo.setPersonalPage("https://www.baidu.com");
        List<String> list = ValidateUtil.valid(userInfo);
        System.out.println(list);
    }
}
  1. 运行结果
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入! ,信息模版:年龄小于 {value} 岁,禁止进入!]

分组校验

  1. 更改 UserInfo
public class UserInfo {
    // 标记接口,新增组:
    public interface Add {}
    public interface Update {}
    // 默认的组:jakarta.validation.groups.Default
    @Null(groups = {Add.class}) // 只适用于新增
    @NotNull(groups = {Update.class}) // 只适用于修改
    private Long id;

    // ...
}
  1. 更改 ValidateUtil
public class ValidateUtil {
    // ...

    public static List<String> valid(UserInfo userInfo, Class<?>... groups) {
        // 如果被校验对象 userInfo 没有校验通过,则 Set 里面就有校验信息:
        Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo, groups);
        return set.stream().map(v -> "属性:" + v.getPropertyPath()
                        + " ,属性的值:" + v.getInvalidValue()
                        + " ,校验不通过的提示信息:" + v.getMessage())
                .toList();
    }
}
  1. 更改 Main
package org.example;

import jakarta.validation.groups.Default;
import org.example.beans.UserInfo;
import org.example.utils.ValidateUtil;

import java.time.LocalDateTime;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("   ");
        userInfo.setAge(17);
        userInfo.setEmail("2530788542@qq.com");
        userInfo.setPhone("13800138000");
        userInfo.setBirthday(LocalDateTime.now().minusDays(1));
        userInfo.setPersonalPage("https://www.baidu.com");
        // 假设现在是新增:
        List<String> list = ValidateUtil.valid(userInfo, UserInfo.Add.class, Default.class); // 应用了自己创建的组,就要加上 Default.class
        System.out.println(list);
        // 假设现在是修改:
        list = ValidateUtil.valid(userInfo, UserInfo.Update.class, Default.class);
        System.out.println(list);
    }
}
  1. 运行结果
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!, 属性:name ,属性的值:    ,校验不通过的提示信息:不能为空]
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!, 属性:name ,属性的值:    ,校验不通过的提示信息:不能为空, 属性:id ,属性的值:null ,校验不通过的提示信息:不能为null]

@Valid 级联校验

  1. 新增 Grade 类
package org.example.beans;

import jakarta.validation.constraints.NotBlank;

public class Grade {
    @NotBlank
    private String no;
}
  1. 更改 Main
public class Main {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("lige");
        userInfo.setAge(18);
        userInfo.setEmail("2530788542@qq.com");
        userInfo.setPhone("13800138000");
        userInfo.setBirthday(LocalDateTime.now().minusDays(1));
        userInfo.setPersonalPage("https://www.baidu.com");
        Grade grade = new Grade(); // 此时并没有设置 grade 的 no 属性值
        userInfo.setGrade(grade);
        List<String> list = ValidationUtil.valid(userInfo, UserInfo.Add.class, Default.class);
        System.out.println(list);
    }
}
  1. 修改 UserInfo
public class UserInfo {
    // ...

    @NotNull
    @Valid // 被引用对象加上 @Valid 注解才可以完成级联校验(让被引用对象的约束注解生效)
    private Grade Grade;

    // ...
}
  1. 运行结果
[属性:name ,属性的值:    ,校验不通过的提示信息:不能为空, 属性:Grade.no ,属性的值:null ,校验不通过的提示信息:不能为空, 属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!]

自定义校验规则(自定义约束注解)与快速失败(failfast)

  1. 新增 UserStatus 注解
package org.example.anno;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = { UserStatusValidator.class }) // 说明当前注解要被谁来完成校验工作
public @interface UserStatus {
    String message() default "status 不能为必须是 1000 | 1001 | 1002 !";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}
  1. 新增 UserStatusValidator 类
package org.example.anno;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.HashSet;
import java.util.Set;

// 该怎么绑定要校验的约束注解呢?
public class UserStatusValidator implements ConstraintValidator<UserStatus, Integer> {
    @Override
    public void initialize(UserStatus constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        Set<Integer> set = new HashSet<>();
        set.add(1000);
        set.add(1001);
        set.add(1002);
        return set.contains(value);
    }
}

  1. 修改 UserInfo
public class UserInfo {
    // ...

    @NotNull
    @UserStatus
    private Integer status;

    // ...
}

  1. 修改 ValidateUtil ,使用 failfast 模式校验
package org.example.utils;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.example.beans.UserInfo;
import org.hibernate.validator.HibernateValidator;

import java.util.List;
import java.util.Set;

// 线程安全:
public class ValidateUtil {
    private static final Validator validator;
    private static final Validator failFastValidator;

    static {
        try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
            validator = validatorFactory.getValidator();
        }
        // 获取配置了快速失败的 Validator :
        try (ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory()) {
            failFastValidator = validatorFactory.getValidator();
        }
    }

    public static List<String> failFastValid(UserInfo userInfo, Class<?>... groups) {
        // 如果被校验对象 userInfo 没有校验通过,则 Set 里面就有校验信息:
        Set<ConstraintViolation<UserInfo>> set = failFastValidator.validate(userInfo, groups);
        return set.stream().map(v -> "属性:" + v.getPropertyPath()
                        + " ,属性的值:" + v.getInvalidValue()
                        + " ,校验不通过的提示信息:" + v.getMessage())
                .toList();
    }

    // ...
}
  1. 修改 Main
public class Main {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("   ");
        userInfo.setAge(17);
        userInfo.setEmail("2530788542@qq.com");
        userInfo.setPhone("13800138000");
        userInfo.setBirthday(LocalDateTime.now().minusDays(1));
        userInfo.setPersonalPage("https://www.baidu.com");
        Grade grade = new Grade(); // 此时并没有设置 grade 的 no 属性值
        userInfo.setGrade(grade);
        userInfo.setStatus(4000);
        List<String> list1 = ValidateUtil.valid(userInfo, UserInfo.Add.class, Default.class);
        System.out.println(list1);
        List<String> list2 = ValidateUtil.failFastValid(userInfo, UserInfo.Add.class, Default.class);
        System.out.println(list2);
    }
}
  1. 运行结果
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!, 属性:name ,属性的值:    ,校验不通过的提示信息:不能为空, 属性:Grade.no ,属性的值:null ,校验不通过的提示信息:不能为空, 属性:status ,属性的值:4000 ,校验不通过的提示信息:status 不能为必须是 1000 | 1001 | 1002 !]
[属性:status ,属性的值:4000 ,校验不通过的提示信息:status 不能为必须是 1000 | 1001 | 1002 !]

非 bean 入参校验

  1. 新增 UserInfoService 类
package org.example.service;

import jakarta.validation.constraints.NotNull;
import org.example.utils.ValidateUtil;

import java.lang.reflect.Method;
import java.util.List;

public class UserInfoService {
    /*
     * 方法非 bean 类型的入参校验步骤:
     * 1. 方法参数前加注解
     * 2. 执行入参校验,真正要用的话,可以使用 aop 编程的思想来使用
     * */
    public String getByName(@NotNull String name) {
        // 执行入参校验:
        StackTraceElement st = Thread.currentThread().getStackTrace()[1];
        String methodName = st.getMethodName(); // getByName
        Method method = null;
        try {
            method = this.getClass().getDeclaredMethod(methodName, String.class);
        } catch(Exception e) {
            e.printStackTrace();
        }
        List<String> list = ValidateUtil.validNotBean(this, method, new Object[]{ name });
        // 打印校验结果:
        System.out.println(list);
        return "ok";
    }
}
  1. 修改 ValidateUtil
package org.example.utils;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.executable.ExecutableValidator;
import org.example.beans.UserInfo;
import org.hibernate.validator.HibernateValidator;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

// 线程安全:
public class ValidateUtil {
    private static final Validator validator;
    private static final Validator failFastValidator;
    private static final ExecutableValidator executableValidator;

    static {
        try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
            validator = validatorFactory.getValidator();
        }
        // 获取配置了快速失败的 Validator :
        try (ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory()) {
            failFastValidator = validatorFactory.getValidator();
        }
        // 获取校验入参或返回值的 Validator :
        try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
            executableValidator = validator.forExecutables();
        }
    }

    public static <T> List<String> validNotBean(T object, Method method, Object[] parameterValues, Class<?>... groups) {
        Set<ConstraintViolation<T>> set = executableValidator.validateParameters(object, method, parameterValues, groups);
        return set.stream().map(v -> "属性:" + v.getPropertyPath()
                        + " ,属性的值:" + v.getInvalidValue()
                        + " ,校验不通过的提示信息:" + v.getMessage())
                .toList();
    }

    // ...
}

  1. 修改 Main
package org.example;

import org.example.service.UserInfoService;

public class Main {
    public static void main(String[] args) {
        UserInfoService userInfoService = new UserInfoService();
        userInfoService.getByName(null);
    }
}
  1. 运行结果
[属性:getByName.arg0 ,属性的值:null ,校验不通过的提示信息:不能为null]

在 Web 环境中使用

搭建 springboot 环境

  1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- ... -->

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.7.6</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
    </dependencies>
</project>
  1. Main
package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class);
    }
}
  1. UserInfoHandler
package org.example.handler;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserInfoHandler {
    @GetMapping("/getByName")
    public String getByName(String name) {
        return name + "ok";
    }
}

使用 @Valid 、 @Validated 在方法或类上进行自动校验

  1. 修改 UserInfoHandler
package com.example.handler;

import com.example.beans.UserInfo;
import com.example.utils.ValidateUtil;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import javax.validation.groups.Default;
import java.util.List;

@RestController
@Validated // 表示整个类都启用校验,如果碰到入参含有 bean validation 注解的话,就会自动校验
public class UserInfoHandler {
    @GetMapping("/getByName")
    public String getByName(String name) {
        return name + "ok";
    }

    // 编程式校验:
    @GetMapping("/addUser1")
    public String addUser1(UserInfo userInfo) {
        List<String> result = ValidateUtil.valid(userInfo);
        if (result.size() > 0) {
            System.out.println(result);
            return "校验不成功!";
        } else {
            return "添加成功!";
        }
    }

    // 在 springMVC 环境中校验一般可以声明式,这种更简单,springMVC 也更推荐:
    @GetMapping("/addUser2")
    public String addUser2(@Valid UserInfo userInfo, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) { // 判断是否有不满足约束的
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println(error.getObjectName() + "::" + error.getDefaultMessage());
            }
            // 获得未通过校验的字段的详情:
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage() + ",当前没通过校验的值是:" + fieldError.getRejectedValue());
            }
            return "校验不通过";
        }
        return "添加成功";
    }

    // 测试 @Validated 分组校验:
    @GetMapping("/addUser3")
    public String addUser3(@Validated({ UserInfo.Update.class, Default.class }) UserInfo userInfo, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) { // 判断是否有不满足约束的
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println(error.getObjectName() + "::" + error.getDefaultMessage());
            }
            // 获得未通过校验的字段的详情:
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage() + ",当前没通过校验的值是:" + fieldError.getRejectedValue());
            }
            return "校验不通过";
        }
        return "添加成功";
    }

    // 不绑定 BindingResult ,则返回默认的 Error 页面:
    @GetMapping("/addUser4")
    public String addUser4(@Validated({ UserInfo.Add.class, Default.class }) UserInfo userInfo) {
        return "addUser4";
    }
}
  1. 修改 UserInfo
package com.example.beans;

import javax.validation.constraints.*;

public class UserInfo {
    // 标记接口,新增组:
    public interface Add {}
    public interface Update {}
    // 默认的组:jakarta.validation.groups.Default
    @Null(groups = {Add.class}) // 只适用于新增
    @NotNull(groups = {Update.class}) // 只适用于修改
    private Long id;

    // name 要求不能是 null , "" , "   " :
    // @NotNull // 只校验不为 null 的
    // @NotEmpty // != null && != "" ,可以为 "   "
    @NotBlank
    private String name;

    // age 要求是正整数,[1, 800] :
    @NotNull
    @Min(value = 18, message = "年龄小于 {value} 岁,禁止进入!")
    private Integer age;

    // Get 、Set 方法……
}

区别 @Valid 和 @Validate

@Valid 支持分组校验,@Validated 支持方法参数的自动校验。

异常处理

  1. 修改 UserInfoHandler
@RestController
public class UserInfoHandler {
    // ...

    // 在每个 Controller 里面写上 @ExceptionHandler 可以处理当前 Controller 里面抛出的 xx 异常:
    @ExceptionHandler(BindException.class)
    public String handleEx(BindException e) {
        List<FieldError> fieldErrors = e.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        for (FieldError fe: fieldErrors) {
            sb.append("属性:").append(fe.getField()).append("校验不通过,原因:").append(fe.getDefaultMessage()).append(";");
        }

        return sb.toString();
    }
}

统一异常处理

  1. 新增 UserInfoHandlerAdvice
package com.example.advice;

import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;

@RestControllerAdvice
public class UserInfoHandlerAdvice {
    // @Validated 注解写在方法上的时候会报这个异常:
    @ExceptionHandler(BindException.class)
    public String handleEx(BindException e) {
        List<FieldError> fieldErrors = e.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        for (FieldError fe: fieldErrors) {
            sb.append("属性:").append(fe.getField()).append("校验不通过,原因:").append(fe.getDefaultMessage()).append(";");
        }

        return sb.toString();
    }

    // @Validated 注解写在类上的时候会报这个异常:
    @ExceptionHandler(ConstraintViolationException.class)
    public List<String> handleEx(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> set = e.getConstraintViolations();
        return set.stream().map(v -> "属性:" + v.getPropertyPath()
                        + " ,属性的值:" + v.getInvalidValue()
                        + " ,校验不通过的提示信息:" + v.getMessage())
                .toList();
    }

    @ExceptionHandler(Exception.class)
    public String handleEx(Exception e) {
        return e.getMessage();
    }
}
posted @ 2023-07-15 16:57  HopeLive  阅读(309)  评论(0)    收藏  举报