Java 代码简介之道 - Bean Validation - hibernate-validator - spring-boot-starter-validation
前言
Jakarta Bean Validation 官网
hibernate-validator 官网
传统的参数校验
- 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 方法……
}
- 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 官网
非 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 初体验
- 给 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 方法……
}
- 定义 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();
}
}
- 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);
}
}
- 运行结果
[属性:name ,属性的值:null ,校验不通过的提示信息:不能为空]
validator 加载原理
org.hibernate.validator.internal.engine.ValidatorImpl
spi :service provider interface ,是 jdk 提供的一种服务发现机制。
常用的校验注解约束
JSR 提供的校验注解:
- 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
- Hibernate Validator 附加的 constraint
@Length(min=, max=) // 被注释的字符串的长度必须在指定的范围 min ~ max
@Range(min=, max=, message=) // 被注释的元素必须在指定的范围 [min, max]
@URL // 一个 URL
- 使用示例
要看一个约束可以作用在哪些类型上,直接点进去看源码开头的注释就行了。
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
自定义消息和消息模版
示例:
- 更改 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;
// ...
}
- 更改 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();
}
}
- 更改 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);
}
}
- 运行结果
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入! ,信息模版:年龄小于 {value} 岁,禁止进入!]
分组校验
- 更改 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;
// ...
}
- 更改 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();
}
}
- 更改 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);
}
}
- 运行结果
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!, 属性:name ,属性的值: ,校验不通过的提示信息:不能为空]
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!, 属性:name ,属性的值: ,校验不通过的提示信息:不能为空, 属性:id ,属性的值:null ,校验不通过的提示信息:不能为null]
@Valid 级联校验
- 新增 Grade 类
package org.example.beans;
import jakarta.validation.constraints.NotBlank;
public class Grade {
@NotBlank
private String no;
}
- 更改 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);
}
}
- 修改 UserInfo
public class UserInfo {
// ...
@NotNull
@Valid // 被引用对象加上 @Valid 注解才可以完成级联校验(让被引用对象的约束注解生效)
private Grade Grade;
// ...
}
- 运行结果
[属性:name ,属性的值: ,校验不通过的提示信息:不能为空, 属性:Grade.no ,属性的值:null ,校验不通过的提示信息:不能为空, 属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!]
自定义校验规则(自定义约束注解)与快速失败(failfast)
- 新增 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 { };
}
- 新增 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);
}
}
- 修改 UserInfo
public class UserInfo {
// ...
@NotNull
@UserStatus
private Integer status;
// ...
}
- 修改 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();
}
// ...
}
- 修改 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);
}
}
- 运行结果
[属性:age ,属性的值:17 ,校验不通过的提示信息:年龄小于 18 岁,禁止进入!, 属性:name ,属性的值: ,校验不通过的提示信息:不能为空, 属性:Grade.no ,属性的值:null ,校验不通过的提示信息:不能为空, 属性:status ,属性的值:4000 ,校验不通过的提示信息:status 不能为必须是 1000 | 1001 | 1002 !]
[属性:status ,属性的值:4000 ,校验不通过的提示信息:status 不能为必须是 1000 | 1001 | 1002 !]
非 bean 入参校验
- 新增 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";
}
}
- 修改 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();
}
// ...
}
- 修改 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);
}
}
- 运行结果
[属性:getByName.arg0 ,属性的值:null ,校验不通过的提示信息:不能为null]
在 Web 环境中使用
搭建 springboot 环境
- 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>
- 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);
}
}
- 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 在方法或类上进行自动校验
- 修改 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";
}
}
- 修改 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 支持方法参数的自动校验。
异常处理
- 修改 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();
}
}
统一异常处理
- 新增 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();
}
}

浙公网安备 33010602011771号