@Validated 使用介绍

embedded/2025/3/25 8:43:18/

说明:在项目开发中,请求进入系统的第一步就是校验,在前后端分离的项目中,有前端校验、后端校验。对于后端开发程序员来说,完全依靠前端校验是不合理的,因为只需要用户知道一点计算机知识,就能使用诸如apifox、postman,附带token调用后端接口,绕过前端校验。

后端校验,一般有以下几类校验:

  • 非空校验:校验对象、数值是否为空,包括不能等于null、或者空字符串;

  • 非法校验:校验数值是否在合法的数值范围内,如自增ID不能为负数,年龄不能为负数,生日不能是未来时间等;

  • 不符合业务逻辑校验:校验数值是否符合业务逻辑,如传入ID,查完数据库发现记录不存在,那后面的逻辑可能就不需要继续了;

  • ……

本文介绍如何使用 @Validated 注解实现对请求参数的校验

搭建环境

首先,搭一个简单的Spring Boot项目,pom如下

<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"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/></parent><groupId>com.hezy</groupId><artifactId>validated_demo</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>validated_demo</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><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>

后面这个依赖,是配合 @Validated 使用的,一些校验数值的注解。

@Validated 校验DTO

如果参数是一个Java Bean对象,里面封装了很多参数,对应Controlled接口如下:

java">    @PostMappingpublic String demo1(@RequestBody @Validated ParamDTO paramDTO) {return "success";}

ParamDTO,里面写了很多属性和校验

java">import com.hezy.annotation.AllUpperCase;
import org.hibernate.validator.constraints.UniqueElements;import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Date;
import java.util.List;/*** 参数DTO* @author hezy* @version 1.0.0* @create 2025/3/22 16:16*/
public class ParamDTO implements Serializable {// 不能为空@NotBlank(message = "name不能为空")@NotEmpty(message = "name不能为空")public String name;// 不能为null,并且在指定范围内@NotNull@Min(value = 1, message = "age不能小于1")@Max(value = 100, message = "age不能大于100")public Integer age;// 长度在指定范围内@Size(min = 2, max = 5, message = "username长度不能小于2或者大于5")public String username;// 符合正则表达式@Pattern(regexp = "^1[3-9]\\d{9}$", message = "请输入有效的中国大陆 11 位手机号码")public String phone;// 整数部分最多 3 位,小数部分最多 2 位@Digits(integer = 3, fraction = 2, message = "amount 的整数部分不能超过 3 位,小数部分不能超过 2 位")public Double amount;// 必须位正数@Positive(message = "score 必须为正数")public Integer score;// 必须为正数或零@PositiveOrZero(message = "balance 必须为正数或零")public Double balance;// 必须为负数@Negative(message = "debt 必须为负数")public Double debt;// 必须为负数或者零@NegativeOrZero(message = "overdraft 必须为负数或零")public Double overdraft;// 必须为未来时间@Future(message = "dueDate 必须是未来的日期")public Date dueDate;// 必须为过去时间@Past(message = "birthDate 必须是过去的日期")public Date birthDate;// 集合内元素必须唯一@UniqueElements(message = "ids 中的元素必须唯一")public List<Integer> ids;// 自定义,字符串必须全为大写@AllUpperCase(message = "字符串必须全为大写")public String uppercaseString;
}

其中:

  • @NotNull:不能为null;

  • @NotBlankL:不能是空格组成的字符串;

  • @NotEmpty:不能为空字符串;

  • @Min(value = 1):长度不能小于1;

  • @Max(value = 100):长度不能大于100;

  • @Size(min = 2, max = 5):长度需要在[2, 5]区间内;

  • @Pattern(regexp = “^1[3-9]\d{9}$”):数值需要符合该正则表达式;

  • @Digits(integer = 3, fraction = 2):浮点型数值,整数部分不能超过3位,小数部分不能超过2位;

  • @Positive():必须是正数;

  • @PositiveOrZero():必须是正数或者零;

  • @Negative():必须是负数;

  • @NegativeOrZero():必须为负数或零;

  • @Future():必须是未来的日期;

  • @Past():必须是过去的日期;

  • @UniqueElements():集合内元素必须唯一;

注解内的message,表示不符合注解规则时,抛出的异常信息。另外 @AllUpperCase 注解是自定义校验规则,校验数值字符串必须全为大写,实现如下:

(先创建一个自定义注解)

java">import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;/*** 自定义注解* @author hezy* @version 1.0.0* @create 2025/3/22*/
@Documented
@Constraint(validatedBy = AllUpperCaseValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AllUpperCase {String message() ;Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

(实现框架的 ConstraintValidator 接口,isValid()方法里面写自己需要校验的逻辑)

java">import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;/*** 自定义验证器* @author hezy* @version 1.0.0* @create 2025/3/22*/
public class AllUpperCaseValidator implements ConstraintValidator<AllUpperCase, String> {@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {return value == null || value.equals(value.toUpperCase());}
}

@Validated 校验单个参数、路径参数

如果接口传入的参数不是用DTO封装的,而是用路径参数,或者直接传递进来的,如下:

java">    @GetMapping("/{id}")public String demo2(@PathVariable @Min(value = 1) Integer id) {return "success";}@GetMappingpublic String demo3(@NotEmpty(message = "name不能为空") String name) {return "success";}

那么,@Validated 注解需要加到类上,像下面这样

java">import com.hezy.pojo.ParamDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;/*** @author hezy* @version 1.0.0* @create 2025/3/22*/
@RestController
@RequestMapping("/validated")
@Validated
public class ValidatedController {@PostMappingpublic String demo1(@RequestBody @Validated ParamDTO paramDTO) {return "success";}@GetMapping("/{id}")public String demo2(@PathVariable @Min(value = 1) Integer id) {return "success";}@GetMappingpublic String demo3(@NotEmpty(message = "name不能为空") String name) {return "success";}
}

创建全局异常处理器

需要另外创建一个全局异常处理器,用于处理参数校验不通过时,直接将注解内的message信息作为请求结果返回,如下:

java">import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;/*** Validated 全局异常处理器* @author hezy* @version 1.0.0* @create 2025/3/22*/
@RestControllerAdvice
public class ValidatedExceptionHandler {/*** DTO中的校验* 处理 @RequestBody 参数校验异常*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(MethodArgumentNotValidException.class)public Map<String, String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {Map<String, String> errors = new HashMap<>();ex.getBindingResult().getAllErrors().forEach((error) -> {String fieldName = ((FieldError) error).getField();String errorMessage = error.getDefaultMessage();errors.put(fieldName, errorMessage);});return errors;}/*** 路径参数或者请求参数中的校验* 处理方法参数校验异常*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(ConstraintViolationException.class)public Map<String, String> handleConstraintViolationException(ConstraintViolationException ex) {Map<String, String> errors = new HashMap<>();ex.getConstraintViolations().forEach((violation) -> {String propertyPath = violation.getPropertyPath().toString();String errorMessage = violation.getMessage();errors.put(propertyPath, errorMessage);});return errors;}
}

如果没有这个处理器,校验不通过的请求会直接返回400状态码,对前端不友好。

启动测试

工作完成了,启动项目,跑两步,调用DTO参数接口,如下:

在这里插入图片描述

可见ids集合中,有元素重复,不符合ids字段的校验,故返回注解中的message信息。如果有多个参数不符合要求,都会返回提示,如下:

在这里插入图片描述


再试下路径参数

在这里插入图片描述

路径参数,参数值最小是1(@Min(value = 1)),我传入0,返回提示,可见提示指明了是哪个方法的哪个参数名不符合要求。再试下直接传入的参数,name要i去不能为空(@NotEmpty(message = "name不能为空")),下面没传,调用返回提示。

在这里插入图片描述

到这里,@Validated 注解的使用基本能覆盖我们大多数场景的参数校验。

另外

另外,抛开参数校验。博主认为,校验是要讲成本的,无休止地校验不能说明你作为一个程序员的成熟,只能说明你对项目业务不熟悉。所以尽量减去不必要的校验,那么,哪些校验是不必要的,这就需要去熟悉业务,把握整个项目,当然也包括项目中使用的框架代码。

总结

本文介绍了在Spring Boot项目中使用@Validated 注解校验接口参数


http://www.ppmy.cn/embedded/176025.html

相关文章

【LeetCode 热题100】 22. 括号生成 的算法思路及python代码

22. 括号生成 数字 n n n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且有效的括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))","(()())","(())()","()(())&…

题单:精挑细选

题目描述 小王是公司的仓库管理员&#xff0c;一天&#xff0c;他接到了这样一个任务&#xff1a;从仓库中找出一根钢管。这听起来不算什么&#xff0c;但是这根钢管的要求可真是让他犯难了&#xff0c;要求如下&#xff1a; 1.1. 这根钢管一定要是仓库中最长的&#xff1b; …

【AVRCP】深度剖析 AVRCP 中 Generic Access Profile 的要求与应用

目录 一、GAP基础架构与核心要求 1.1 GAP在蓝牙体系中的定位 1.2 核心模式定义 二、AVRCP对GAP的增强要求 2.1 模式扩展规范 2.2 空闲模式过程支持 三、安全机制实现细节 3.1 认证与加密流程 3.2 安全模式要求 四、设备发现与连接建立 4.1 发现过程状态机 4.2 连接…

dify创建第一个Agent

1、首先LLM模型必须支持 Function Calling 由于deepseek-R1本地化部署时还不支持&#xff0c;所以使用 qwq模型。 2、创建空白 Agent 3、为Agent添加工具 4、测试 当未添加时间工具时 询问 时间 如下 5、开启时间工具 询问如下

第十六章:Specialization and Overloading_《C++ Templates》notes

Specialization and Overloading 一、模板特化与重载的核心概念二、代码实战与测试用例三、关键知识点总结四、进阶技巧五、实践建议多选题设计题代码测试说明 一、模板特化与重载的核心概念 函数模板重载 (Function Template Overloading) // 基础模板 template<typename…

神聖的綫性代數速成例題11. 極大綫性無關組、矢量在一組基下的座標

極大綫性無關組&#xff1a;設S是一個矢量組&#xff0c;T是S的一個部分矢量組。如果T綫性無關&#xff0c;且S中的任意矢量都可以由T綫性表示&#xff0c;則稱T是S的一個極大綫性無關組。極大綫性無關組不唯一&#xff0c;但極大綫性無關組中矢量的個數是唯一的&#xff0c;稱…

IDEA 快捷键ctrl+shift+f 无法全局搜索内容的问题及解决办法

本篇文章主要讲解IDEA、phpStrom、webStrom、pyCharm等jetbrains系列编辑器无法进行全局搜索内容问题的主要原因及解决办法。 日期&#xff1a;2025年3月22日 作者&#xff1a;任聪聪 现象描述&#xff1a; 1.按下ctrlshiftf 输入法转为了繁体。 2.快捷键ctrlshiftr 可以全局检…

GGUF 和 llama.cpp 是什么关系

这是个非常关键的问题&#xff0c;咱们来细说下&#xff1a;GGUF 和 llama.cpp 是什么关系&#xff0c;它们各自干什么&#xff0c;如何配合工作。 &#x1f527; 一、llama.cpp 是什么&#xff1f; llama.cpp 是 Meta 的开源大语言模型 LLaMA&#xff08;Language Model from…