当前位置: 首页 > news >正文

Spring Boot 参数校验 Validation 终极指南

1. 概述

Spring Validation 基于 JSR-303(Bean Validation)规范,通过@Validated注解实现声明式校验。核心优势:

  • 零侵入性:基于 AOP 实现方法拦截校验
  • 规范统一:兼容 Bean Validation 标准注解
  • 功能扩展:支持分组校验、嵌套校验等高级特性
  • 高效开发:减少 80% 的参数校验代码量

💡 关键区别:@Validated是 Spring 对@Valid的增强封装,支持分组校验,而@Valid支持嵌套校验

2. 注解体系

2.1 Bean Validation 标准注解

分类注解说明
空值检查@NotBlank字符串非空且 trim() 后长度 > 0(仅适用于字符串)
@NotEmpty集合/数组元素数 > 0,字符串长度 > 0(适用于集合、数组、字符串)
@NotNull字段值不能为 null
@Null字段值必须为 null
数值检查@DecimalMax(value)数值必须 ≤ 指定值(支持小数)
@DecimalMin(value)数值必须 ≥ 指定值(支持小数)
@Digits(integer,fraction)整数部分最多 integer 位,小数部分最多 fraction
@Positive必须为正数
@PositiveOrZero必须为正数或 0
@Max(value)数值必须 ≤ 指定值(仅限整数)
@Min(value)数值必须 ≥ 指定值(仅限整数)
@Negative必须为负数
@NegativeOrZero必须为负数或 0
布尔检查@AssertTrue必须为 true
@AssertFalse必须为 false
长度检查@Size(min,max)字符串/集合/数组长度在 [min,max] 范围内
日期检查@Future必须是将来日期
@FutureOrPresent必须是将来或当前日期
@Past必须是过去日期
@PastOrPresent必须是过去或当前日期
其他检查@Email符合邮箱格式(可配置宽松模式)
@Pattern(regexp)符合正则表达式

2.2 Hibernate Validator 扩展注解

分类注解说明
范围检查@Range(min,max)数值必须在 [min,max] 范围内(支持整型、BigDecimal)
字符串检查@Length(min,max)字符串长度在 [min,max] 范围内
格式检查@URL合法 URL 格式(可指定协议/主机/端口等参数)
安全校验@SafeHtml过滤危险 HTML 标签(防御 XSS 攻击)
其他检查@LuhnCheck银行卡号校验(Luhn 算法)
@CNPJ巴西企业税号校验
@CPF巴西个人税号校验

2.3 @Valid vs @Validated

特性@Valid@Validated
分组校验❌ 不支持✅ 支持
嵌套校验✅ 支持❌ 不支持
校验触发自动触发需配合AOP使用

3. 快速入门

3.1 添加依赖

<dependencies><!-- 实现对 Spring MVC 的自动化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 保证 Spring AOP 相关的依赖包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></dependency><!-- 方便等会写单元测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>
  • spring-boot-starter-web 依赖里,已经默认引入 hibernate-validator 依赖,所以本示例使用的是 Hibernate Validator 作为 Bean Validation 的实现框架。

3.2 DTO 对象示例

public class UserAddDTO {/*** 账号*/@NotEmpty(message = "登录账号不能为空")@Length(min = 5, max = 16, message = "账号长度为 5-16 位")@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")private String username;/*** 密码*/@NotEmpty(message = "密码不能为空")@Length(min = 4, max = 16, message = "密码长度为 4-16 位")private String password;// ... 省略 setting/getting 方法
}

3.3 启用校验

@RestController
@RequestMapping("/users")
@Validated
public class UserController {private Logger logger = LoggerFactory.getLogger(getClass());@GetMapping("/get")public void get(@RequestParam("id") @Min(value = 1L, message = "编号必须大于 0") Integer id) {logger.info("[get][id: {}]", id);}@PostMapping("/add")public void add(@Valid UserAddDTO addDTO) {logger.info("[add][addDTO: {}]", addDTO);}}

4. 统一异常处理

4.1 @Valid 的异常处理

当使用 @Valid 注解进行参数校验时,校验失败会抛出 MethodArgumentNotValidException
全局拦截示例:

@RestControllerAdvice
public class GlobalExceptionHandler {/**** 触发场景* 对象参数校验失败(如 @RequestBody + @Valid)* 常见使用组合* @Valid + DTO 对象* 校验注解适用对象* 对象属性级校验(@NotNull/@Size 等)*/@ExceptionHandler(value = MethodArgumentNotValidException.class)public Result handleValidException(MethodArgumentNotValidException e) {BindingResult bindingResult = e.getBindingResult();List<String> errors = bindingResult.getFieldErrors().stream().map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()).collect(Collectors.toList());return Result.error(400, "参数校验失败", errors);}
}

4.2 @Validated 的异常处理

当使用 @Validated 注解时,需要分场景处理:

场景 1:Controller 层方法参数校验

如果直接在 Controller 方法参数上使用 @Validated 校验简单类型(如 @RequestParam、@PathVariable),校验失败会抛出 ConstraintViolationException
全局拦截示例:

@RestControllerAdvice
public class GlobalExceptionHandler {/**** 触发场景* 方法参数直接校验* 常见使用组合* @Validated + 方法参数校验* 校验注解适用对象* 方法参数级校验(@RequestParam + @NotBlank 等)* 需要在类上标注 @Validated 才能触发 ConstraintViolationException*/@ExceptionHandler(ConstraintViolationException.class)public Result handleConstraintViolation(ConstraintViolationException e) {List<String> errors = e.getConstraintViolations().stream().map(v -> v.getPropertyPath() + ": " + v.getMessage()).collect(Collectors.toList());return Result.error(400, "参数校验失败", errors);}}

场景 2:校验对象参数

如果校验对象参数(如 @RequestBody),行为与 @Valid 一致,抛出 MethodArgumentNotValidException(处理方式同 @Valid)。

完整异常处理配置

@RestControllerAdvice
public class GlobalExceptionHandler {/**** 触发场景* 对象参数校验失败 如 (@RequestBody + @Valid/@RequestBody + @Validated)* 常见使用组合* (@Valid + DTO 对象/@Validated + DTO 对象)* 校验注解适用对象* 对象属性级校验(@NotNull/@Size 等)*/@ExceptionHandler(MethodArgumentNotValidException.class)public Result handleMethodArgumentNotValid(MethodArgumentNotValidException e) {BindingResult result = e.getBindingResult();List<String> errors = result.getFieldErrors().stream().map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()).collect(Collectors.toList());return Result.error(400, "对象参数校验失败", errors);}/**** 触发场景* 方法参数直接校验* 常见使用组合* @Validated + 方法参数校验* 校验注解适用方法参数* 方法参数级校验(@RequestParam + @NotBlank 等)* 需要在类上标注 @Validated 才能触发 ConstraintViolationException*/@ExceptionHandler(ConstraintViolationException.class)public Result handleConstraintViolation(ConstraintViolationException e) {List<String> errors = e.getConstraintViolations().stream().map(v -> v.getPropertyPath() + ": " + v.getMessage()).collect(Collectors.toList());return Result.error(400, "简单参数校验失败", errors);}
}

4.3 关键总结

注解使用场景抛出异常
@Valid校验对象参数(如 @RequestBody)MethodArgumentNotValidException
@Validated校验简单类型参数(如 @RequestParam)ConstraintViolationException
@Validated校验对象参数(需配合 @Valid 使用)MethodArgumentNotValidException

5. 自定义约束

在大多数项目中,无论是 Bean Validation 定义的约束,还是 Hibernate Validator 附加的约束,都是无法满足我们复杂的业务场景。所以,我们需要自定义约束。

开发自定义约束一共只要两步:

  • 1)编写自定义约束的注解;
  • 2)编写自定义的校验器 ConstraintValidator 。

下面,就让我们一起来实现一个自定义约束,用于校验参数必须在枚举值的范围内。

5.1 ArrayValuable

public interface ArrayValuable<T> {/*** @return 数组*/T[] array();
} 

5.2 CommonStatusEnum


@Getter
@AllArgsConstructor
public enum CommonStatusEnum implements ArrayValuable<Integer> {ENABLE(0, "开启"),DISABLE(1, "关闭");public static final Integer[] ARRAYS = Arrays.stream(values()).map(CommonStatusEnum::getStatus).toArray(Integer[]::new);/*** 状态值*/private final Integer status;/*** 状态名*/private final String name;@Overridepublic Integer[] array() {return ARRAYS;}}

5.3 @InEnum

@Target({ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {InEnumValidator.class}
)
public @interface InEnum {/*** @return 实现 ArrayValuable 接口的类*/Class<? extends ArrayValuable<?>> value();String message() default "必须在指定范围 {value}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}

5.4 InEnumValidator

public class InEnumValidator implements ConstraintValidator<InEnum, Object> {private List<?> values;@Overridepublic void initialize(InEnum annotation) {ArrayValuable<?>[] values = annotation.value().getEnumConstants();if (values.length == 0) {this.values = Collections.emptyList();} else {this.values = Arrays.asList(values[0].array());}}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext context) {// 为空时,默认不校验,即认为通过if (value == null) {return true;}// 校验通过if (values.contains(value)) {return true;}// 校验不通过,自定义提示语句context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate().replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句return false;}}

5.5 UserUpdateStatusDTO

public class UserUpdateStatusDTO{/*** 用户编号*/@NotNull(message = "用户编号不能为空")private Integer id;/*** 状态*/@NotNull(message = "状态不能为空")@InEnum(value = CommonStatusEnum .class, message = "状态必须是 {value}")private Integer status;// ... 省略 set/get 方法
}

5.6 UserController

@PostMapping("/update_status")
public void updateStatus(@Valid UserUpdateStatusDTO updateStatusDTO) {logger.info("[updateStatus][updateStatusDTO: {}]", updateStatusDTO);
}

6. 分组校验

6.1 UserUpdateStatusDTO

public class UserUpdateStatusDTO {/*** 分组 01 ,要求状态必须为 true*/public interface Group01 {}/*** 状态 02 ,要求状态必须为 false*/public interface Group02 {}/*** 状态*/@AssertTrue(message = "状态必须为 true", groups = Group01.class)@AssertFalse(message = "状态必须为 false", groups = Group02.class)private Boolean status;// ... 省略 set/get 方法
}

6.2 UserController

@PostMapping("/update_status_true")
public void updateStatusTrue(@Validated(UserUpdateStatusDTO.Group01.class) UserUpdateStatusDTO updateStatusDTO) {logger.info("[updateStatusTrue][updateStatusDTO: {}]", updateStatusDTO);
}@PostMapping("/update_status_false")
public void updateStatusFalse(@Validated(UserUpdateStatusDTO.Group02.class) UserUpdateStatusDTO updateStatusDTO) {logger.info("[updateStatusFalse][updateStatusDTO: {}]", updateStatusDTO);
}

7. 手动触发校验

@Service 
public class ManualValidateService {@Autowiredprivate Validator validator;public void validate(UserAddDTO addDTO) {Set<ConstraintViolation<UserAddDTO>> result = validator.validate(addDTO);// 打印校验结果 // <4>for (ConstraintViolation<UserAddDTO> constraintViolation : result) {// 属性:消息System.out.println(constraintViolation.getPropertyPath() + ":" + constraintViolation.getMessage());}}
}

掌握这些核心要点,你的 Spring Boot 参数校验体系将兼具 健壮性可维护性

相关文章:

  • Open AI 使用篇
  • 从拥堵到畅行,智慧城市如何实现交通魔法?
  • 深入剖析Java中ThreadLocal原理
  • 可灵 2.0 可图 2.0 全解析:从 AI 图片到影视级视频的创作革命
  • zkmall模块商城:B2C 场景下 Vue3 前端性能优化的广度探索与实践
  • ThermoMPNN/ThermoMPNN-D 的安装及使用教程
  • Google-A2A协议全面解析:一文掌握Agent-to-Agent协议的核心与应用
  • 客户消失不配合,如何推动验收
  • 信号与系统期中复习(第一章)
  • 如何建立可复用的项目管理模板
  • 基于STM32与NB-IoT的智慧路灯远程监控系统
  • Missashe考研日记-day21
  • Daily morning reading 26
  • 贪心算法day9(合并区间)
  • Redisson分布式锁全攻略:用法、场景与要点
  • Linux 使用Nginx搭建简易网站模块
  • Sentinel源码—2.Context和处理链的初始化一
  • Android 开发 如何生成系统签名
  • CRUD2
  • QuickAPI 核心能力解析:构建数据服务化的三位一体生态
  • 郑州卫健委通报郑飞医院“血液净化”问题:拟撤销该院血液净化技术备案
  • 秦洪看盘|新热点涌现,A股活力渐显
  • 谁在贩卖个人信息?教培机构信息失守,电商平台“订单解密”
  • 中国政府援缅第七批抗震救灾物资运抵交付
  • 女子伸腿阻止高铁关门被拘,央媒:严格依规公开处理以儆效尤
  • 海拔四百公里的救赎