一、@Validated校验概述
1.1 核心说明
@Validated是 Spring 框架对 JSR303/JSR380(Java 原生参数校验规范)的增强注解 ,Spring 生态专属注解,功能全面优于JSR原生的@Valid- JSR303/JSR380 是Java官方定义的参数校验标准,提供基础校验注解,核心依赖包为
jakarta.validation - 实际开发中依赖 Hibernate Validator,是JSR规范的最优实现,扩展了更多实用校验注解,是校验功能落地的核心
- 核心价值:彻底替代硬编码的
if/else参数判断逻辑,将参数校验规则与业务逻辑解耦,所有校验规则统一维护在实体类中,大幅精简业务代码 - 核心区别:
@Validated完全兼容@Valid,核心增强了分组校验 能力,是Spring MVC/SpringBoot项目中Bean参数校验的首选方案
1.2 必备Maven依赖
注意:SpringBoot 2.3.x 及以上版本,
spring-boot-starter-web移除了校验相关依赖,必须手动引入;SpringBoot 2.2.x 及以下版本,仅引入spring-boot-starter-web即可,内置所有校验依赖。
xml
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>5.0.0</version>
</dependency>
二、@Validated 基础核心用法
核心执行流程
- 在需要校验的Java Bean实体类的属性上,添加校验注解配置校验规则+失败提示文案
- 在Controller接口的Bean入参前,添加
@Validated注解标记,Spring MVC自动触发参数校验 - 校验通过 → 正常执行Controller中的业务逻辑
- 校验失败 → 触发校验异常,返回配置的失败提示信息
步骤1:定义实体类,配置校验注解
校验规则的核心配置处,所有校验注解配置在实体属性上,通过message属性自定义校验失败的提示信息,所有校验注解均来自jakarta.validation.constraints包。
java
@Data
public class User {
// 用户ID:不能为空 + 必须为正整数
@NotNull(message = "用户ID不能为空")
@Positive(message = "用户ID必须为正整数")
private Long id;
// 用户名:不能为空+非空字符串+长度2-10位,最常用的字符串非空校验
@NotBlank(message = "用户名不能为空")
@Length(min = 2, max = 10, message = "用户名长度需要在2~10个字符之间")
private String username;
// 密码:不能为空+长度6-20位
@NotBlank(message = "登录密码不能为空")
@Size(min = 6, max = 20, message = "密码长度需要在6~20个字符之间")
private String password;
// 邮箱:非必填,填写则必须符合合法邮箱格式
@Email(message = "邮箱格式不正确,请填写有效邮箱")
private String email;
// 手机号:非必填,填写则匹配手机号正则规则
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确,需填写11位有效手机号")
private String phone;
// 年龄:必填 + 取值范围18-60
@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 60, message = "年龄不能大于60岁")
private Integer age;
}
步骤2:Controller层使用@Validated触发校验(两种标准写法)
写法一:手动捕获校验结果 - BindingResult(入门推荐)
在@Validated标记的入参后,紧跟BindingResult参数,Spring会自动将校验结果封装到该对象中,不会抛出全局异常,适合需要个性化返回校验结果的场景。
硬性规则:
BindingResult参数必须紧跟 在被@Validated注解标记的参数之后,一一对应,否则无法捕获校验结果。
java
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Validated;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class UserController {
@PostMapping("/user/save")
public Map<String, Object> saveUser(@RequestBody @Validated User user, BindingResult bindingResult) {
Map<String, Object> result = new HashMap<>(3);
result.put("code", 200);
result.put("msg", "操作成功");
// 判断是否存在参数校验失败
if (bindingResult.hasErrors()) {
result.put("code", 400);
result.put("msg", "参数校验失败");
Map<String, String> errorMap = new HashMap<>();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
// 封装校验失败的字段和对应提示信息
for (FieldError error : fieldErrors) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
result.put("data", errorMap);
return result;
}
// 校验通过,执行业务逻辑
result.put("data", user);
return result;
}
}
写法二:纯注解触发校验(生产最佳实践)
Controller层只需要在入参前添加@Validated注解,无需编写任何校验相关代码,校验失败的异常由全局统一异常处理器捕获处理,代码极致精简,无冗余,是项目开发的标准写法。
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Validated;
import java.util.HashMap;
import java.util.Map;
@RestController
public class UserController {
@PostMapping("/user/update")
public Map<String, Object> updateUser(@RequestBody @Validated User user) {
Map<String, Object> result = new HashMap<>(3);
result.put("code", 200);
result.put("msg", "用户信息修改成功");
result.put("data", user);
return result;
}
}
三、全局统一异常处理器
核心说明
- 使用
@RestControllerAdvice注解定义全局异常处理器,作用于项目中所有的Controller层接口 - 使用
@ExceptionHandler注解指定要捕获的异常类型,Bean参数校验失败抛出的核心异常为MethodArgumentNotValidException - 统一封装接口返回格式,所有接口的异常返回结构完全一致,前端无需单独适配不同接口的异常格式
- 可添加兜底异常捕获,处理项目中其他未捕获的系统异常
java
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 全局参数校验异常处理器
* 统一捕获所有@Validated注解的Bean参数校验失败异常
*/
@RestControllerAdvice
public class GlobalValidExceptionHandler {
/**
* 捕获Bean参数校验失败的核心异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> handleValidException(MethodArgumentNotValidException e) {
Map<String, Object> result = new HashMap<>(3);
result.put("code", 400);
result.put("msg", "参数校验失败");
FieldError fieldError = e.getBindingResult().getFieldError();
Map<String, String> errorInfo = new HashMap<>(1);
if (fieldError != null) {
errorInfo.put(fieldError.getField(), fieldError.getDefaultMessage());
}
result.put("data", errorInfo);
return result;
}
/**
* 兜底捕获项目中所有未处理的系统异常
*/
@ExceptionHandler(Exception.class)
public Map<String, Object> handleSystemException(Exception e) {
Map<String, Object> result = new HashMap<>(3);
result.put("code", 500);
result.put("msg", "系统内部异常,请稍后重试");
result.put("data", e.getMessage());
return result;
}
}
四、常用校验注解大全
所有注解均来自包 jakarta.validation.constraints,按使用频率排序,重点高频注解加粗标注,所有注解添加`标识
4.1 空值/空串校验(最高频)
@NotBlank:属性值不能为null + 不能为空字符串 + 不能全是空格,仅支持String类型,推荐用于用户名、密码、手机号等核心字符串字段@NotNull:属性值不能为null,支持所有数据类型,对空字符串""不生效,适合所有非字符串类型的非空校验@NotEmpty:属性值不能为null + 长度/元素个数大于0,支持String、数组、集合、Map类型
4.2 长度/范围校验
@Length(min=?,max=?):指定字符串的长度区间,仅支持String类型@Size(min=?,max=?):指定集合/数组的元素个数区间、字符串的长度区间,多类型兼容,通用性强@Range(min=?,max=?):指定数值类型的取值区间,支持Integer、Long、Double等数值类型
4.3 数值合法性校验
@Min(value=?):数值必须大于等于指定值@Max(value=?):数值必须小于等于指定值@Positive:数值必须为正整数(> 0)@PositiveOrZero:数值必须为正整数或0(≥ 0)@Negative:数值必须为负整数(< 0)@Digits(integer=?,fraction=?):限制数值的整数位和小数位长度,适合金额、百分比等场景
4.4 格式校验(常用)
@Email:校验字符串是否为合法的邮箱格式,可自定义正则补充校验规则@Pattern(regexp=?):自定义正则表达式校验,万能注解,适合手机号、身份证号、邮编、验证码等个性化格式校验@Past:日期类型必须是过去的时间,适合生日、创建时间等场景@Future:日期类型必须是未来的时间,适合预约时间、过期时间等场景
五、@Validated 高级用法
5.1 分组校验
适用场景
同一个实体类,在新增、修改、查询等不同的业务场景下,需要的校验规则不同。例如:新增用户时ID无需校验(自增),修改用户时ID必须必填且合法;新增时密码必填,修改时密码非必填。
核心原理
给校验注解添加groups属性指定分组标识,在Controller层的@Validated中指定要生效的分组,即可按需触发不同的校验规则,实现一套实体类适配多套校验规则。
步骤1:定义分组标识(空接口,仅做标记)
java
/**
* 校验分组标记接口,仅做分组标识使用,无需实现任何方法
*/
// 新增业务场景分组
public interface AddGroup {}
// 修改业务场景分组
public interface UpdateGroup {}
步骤2:实体类中为校验注解指定分组
java
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
@Data
public class User {
// 仅修改场景校验ID,新增场景不校验
@NotNull(message = "用户ID不能为空", groups = UpdateGroup.class)
@Positive(message = "用户ID必须为正整数", groups = UpdateGroup.class)
private Long id;
// 新增+修改场景都需要校验,指定多个分组
@NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
private String username;
// 仅新增场景校验密码,修改场景不校验
@NotBlank(message = "密码不能为空", groups = AddGroup.class)
private String password;
}
步骤3:Controller层指定分组触发校验
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Validated;
import java.util.HashMap;
import java.util.Map;
@RestController
public class UserController {
// 新增用户:仅触发AddGroup分组的校验规则
@PostMapping("/user/add")
public Map<String, Object> addUser(@RequestBody @Validated(AddGroup.class) User user) {
Map<String, Object> result = new HashMap<>(3);
result.put("code", 200);
result.put("msg", "用户新增成功");
result.put("data", user);
return result;
}
// 修改用户:仅触发UpdateGroup分组的校验规则
@PutMapping("/user/edit")
public Map<String, Object> editUser(@RequestBody @Validated(UpdateGroup.class) User user) {
Map<String, Object> result = new HashMap<>(3);
result.put("code", 200);
result.put("msg", "用户修改成功");
result.put("data", user);
return result;
}
}
5.2 嵌套校验
适用场景
实体类中包含另一个实体类的对象属性,需要对嵌套的子实体属性也进行参数校验。例如:User实体包含Address收货地址实体,新增用户时需要同时校验用户信息和收货地址信息。
核心规则
嵌套的子实体属性上必须添加@Valid注解,外层的Controller入参添加@Validated注解,即可触发多层级的嵌套校验,缺一不可。
java
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
// 子实体:收货地址
@Data
public class Address {
@NotBlank(message = "收货地址不能为空")
private String addressDetail;
@NotBlank(message = "收货人姓名不能为空")
private String receiverName;
@NotBlank(message = "收货人手机号不能为空")
private String receiverPhone;
}
// 主实体:用户信息
@Data
public class User {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
// 嵌套子实体,必须添加@Valid注解触发嵌套校验
@Valid
private Address address;
}
5.3 自定义校验注解(个性化业务校验)
适用场景
JSR标准注解无法满足业务的个性化校验需求,例如:手机号格式校验、状态值只能是0/1、身份证号校验、性别只能是男/女等场景,需要自定义校验规则。
步骤1:自定义校验注解
java
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 指定校验规则的实现类
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface ValidPhone {
// 校验失败的默认提示信息
String message() default "手机号格式不正确,请填写11位有效手机号";
// 分组校验必备属性,默认空即可
Class<?>[] groups() default {};
// 负载属性,默认空即可
Class<? extends Payload>[] payload() default {};
}
步骤2:编写校验规则实现类
java
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
/**
* 手机号校验规则实现类
* 实现ConstraintValidator接口,泛型:自定义注解类,校验的属性类型
*/
public class PhoneNumberValidator implements ConstraintValidator<ValidPhone, String> {
// 手机号正则表达式
private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
// 手机号为空时不校验,如需必填可配合@NotBlank注解使用
if (phone == null || phone.trim().isEmpty()) {
return true;
}
// 匹配正则表达式
return Pattern.matches(PHONE_REGEX, phone);
}
}
步骤3:实体类中使用自定义校验注解
java
import lombok.Data;
@Data
public class User {
private String username;
private String password;
// 使用自定义的手机号校验注解
@ValidPhone(message = "请填写正确的11位手机号")
private String phone;
}
六、常见问题
6.1 校验注解不生效的常见原因
- SpringBoot 2.3.x+版本缺少校验依赖,未手动引入hibernate-validator核心包,是最常见的原因
- Controller层的入参前忘记添加
@Validated注解,仅实体配置校验注解不会触发校验 - 校验注解使用错误:例如给非String类型使用
@NotBlank、给空字符串使用@NotNull、注解配置在getter/setter方法上而非属性上 - 分组校验时,校验注解指定了
groups分组,但@Validated中未指定对应的分组,导致注解不生效
6.2 @Validated 与 @Valid 的核心区别
@Validated是Spring框架的注解,支持分组校验 ;@Valid是JSR原生注解,不支持分组校验- 嵌套校验时,子实体的属性上必须使用
@Valid注解,外层触发校验使用@Validated - 纯基础校验场景下,两者的校验效果完全一致,可互换使用
七、核心总结
@Validated是Spring MVC中Bean参数校验的标准注解,核心价值是解耦校验规则与业务逻辑,替代硬编码的参数判断- 基础用法:实体类配置校验注解 + Controller入参加
@Validated+ 全局异常处理器,是生产项目的标准写法,适配99%的业务场景 - 复杂场景:分组校验解决多业务场景复用实体、嵌套校验解决多层级实体校验、自定义注解解决个性化业务校验
- 后端参数校验是项目的基础规范,既能保证数据合法性,又能让业务代码更简洁、易维护、易扩展