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

@Validated 使用介绍

说明:在项目开发中,请求进入系统的第一步就是校验,在前后端分离的项目中,有前端校验、后端校验。对于后端开发程序员来说,完全依靠前端校验是不合理的,因为只需要用户知道一点计算机知识,就能使用诸如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接口如下:

    @PostMapping
    public String demo1(@RequestBody @Validated ParamDTO paramDTO) {
        return "success";
    }

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

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 注解是自定义校验规则,校验数值字符串必须全为大写,实现如下:

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

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()方法里面写自己需要校验的逻辑)

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> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || value.equals(value.toUpperCase());
    }
}

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

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

    @GetMapping("/{id}")
    public String demo2(@PathVariable @Min(value = 1) Integer id) {
        return "success";
    }

    @GetMapping
    public String demo3(@NotEmpty(message = "name不能为空") String name) {
        return "success";
    }

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

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 {

    @PostMapping
    public String demo1(@RequestBody @Validated ParamDTO paramDTO) {
        return "success";
    }

    @GetMapping("/{id}")
    public String demo2(@PathVariable @Min(value = 1) Integer id) {
        return "success";
    }

    @GetMapping
    public String demo3(@NotEmpty(message = "name不能为空") String name) {
        return "success";
    }
}

创建全局异常处理器

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

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 注解校验接口参数

相关文章:

  • Android开源库——Glide
  • Java主流开发框架之请求响应常用注释
  • 【LeetCode】大厂面试算法真题回忆(36)--相同数字的积木游戏
  • FFmpeg + ‌Qt‌ 简单视频播放器代码
  • 【算法笔记】图论基础(一):建图、存图、树和图的遍历、拓扑排序、最小生成树
  • 如何在 Bash 中不依赖 curl 或 wget 发出 HTTP 请求并实现文件传输——/dev/tcp的妙用
  • illustrate:一款蛋白/核酸结构快速渲染为“卡通风格”的小工具
  • Ciura序列
  • 弱网测试:全链路实战、高阶策略与自动化落地
  • 多线程14(哈希表与文件操作IO)
  • CPU架构和微架构
  • 中颖SH366000介绍和使用全解
  • Web安全策略CSP详解与实践
  • HTTP请求过程详解
  • 构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据
  • /2要求:定义一个方法,根据id查找对应的用户信息 //如果存在,返回id //如果不存在,返回-1
  • 蓝桥杯 小球反弹
  • 278.缀点成线
  • uniapp 和 webview 之间的通信
  • 【1】Java 零基础入门学习(小白专用)
  • 西藏阿里地区日土县连发两次地震,分别为4.8级和3.8级
  • 今年一季度全国城镇新增就业308万人,就业形势保持总体稳定
  • 路边“僵尸车”被人以1450元卖了,嫌疑人被刑拘
  • 监狱法修订草案提请全国人大常委会会议审议
  • 中公教育薪酬透视:董监高合计涨薪122万,员工精简近三成
  • 我的科学观|张峥:AI快速迭代,我们更需学会如何与科技共处