Spring Boot 参数校验 Validation 终极指南

news2025/4/18 15:47:18

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;

    @Override
    public 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;

    @Override
    public 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());
        }
    }

    @Override
    public 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 {
	@Autowired
	private 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 参数校验体系将兼具 健壮性可维护性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2336649.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Policy Gradient思想、REINFORCE算法,以及贪吃蛇小游戏(一)

文章目录 Policy Gradient思想论文REINFORCE算法论文Policy Gradient思想和REINFORCE算法的关系用一句人话解释什么是REINFORCE算法策略这个东西实在是太抽象了,它可以是一个什么我们能实际感受到的东西?你说的这个我理解了,但这个东西,我怎么优化?在一堆函数中,找到最优…

Profibus DP主站转modbusTCP网关与dp从站通讯案例

Profibus DP主站转modbusTCP网关与dp从站通讯案例 在当前工业自动化的浪潮中&#xff0c;不同协议之间的通讯转换成为了提升生产效率和实现设备互联的关键。Profibus DP作为一种广泛应用的现场总线技术&#xff0c;与Modbus TCP的结合&#xff0c;为工业自动化系统的集成带来了…

快速部署大模型 Openwebui + Ollama + deepSeek-R1模型

背景 本文主要快速部署一个带有web可交互界面的大模型的应用&#xff0c;主要用于开发测试节点&#xff0c;其中涉及到的三个组件为 open-webui Ollama deepSeek开放平台 首先 Ollama 是一个开源的本地化大模型部署工具,提供与OpenAI兼容的Api接口&#xff0c;可以快速的运…

H.265硬件视频编码器xk265代码阅读 - 帧内预测

源代码地址&#xff1a; https://github.com/openasic-org/xk265 帧内预测具体逻辑包含在代码xk265\rtl\rec\rec_intra\intra_pred.v 文件中。 module intra_pred() 看起来是每次计算某个4x4块的预测像素值。 以下代码用来算每个pred_angle的具体数值&#xff0c;每个mode_i对应…

Arcgis经纬线标注设置(英文、刻度显示)

在arcgis软件中绘制地图边框&#xff0c;添加经纬度度时常常面临经纬度出现中文&#xff0c;如下图所示&#xff1a; 解决方法&#xff0c;设置一下Arcgis的语言 点击高级--确认 这样Arcgis就转为英文版了&#xff0c;此时在来看经纬线刻度的标注&#xff0c;自动变成英文

Windows安装Ollama并指定安装路径(默认C盘)

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;http://blog.csdn.net/q258523454/article/details/147289192 一、下载Ollama 访问Ollama官网 打开浏览器&#xff0c;访问Ollama的官方网站&#xff1a;https://ollama.ai/。 在官网首页…

Python自动化处理奖金分摊:基于连续空值的智能分配算法升级

Python自动化处理奖金分摊&#xff1a;基于连续空值的智能分配算法升级 原创 IT小本本 IT小本本 2025年04月04日 02:00 北京 引言 在企业薪酬管理中&#xff0c;团队奖金分配常涉及复杂的分摊规则。传统手工分摊不仅效率低下&#xff0c;还容易因人为疏漏导致分配不公。 本文…

AI工具箱源码+成品网站源码+springboot+vue

大家好&#xff0c;今天给大家分享一个靠AI广告赚钱的项目&#xff1a;AI工具箱成品网站源码&#xff0c;源码支持二开&#xff0c;但不允许转售&#xff01;&#xff01; 本人专门为小型企业和个人提供的解决方案。 不懂技术的也可以直接部署工具箱网站&#xff0c;成为站长&…

如何下载免费地图数据?

按照以下步骤下载免费地图数据。 1、安装GIS地图下载器 从GeoSaaS&#xff08;.COM&#xff09;官网下载“GIS地图下载器”软件&#xff1a;&#xff0c;安装完成后桌面上出现”GIS地图下载器“图标。 双击桌面图标打开”GIS地图下载器“ 2、下载地图数据 点击主界面底部的“…

IO 口作为外部中断输入

外部中断 1. NVIC2. EXTI 1. NVIC NVIC即嵌套向量中断控制器&#xff0c;它是内核的器件&#xff0c;M3/M4/M7 内核都是支持 256 个中断&#xff0c;其中包含了 16 个系统中断和 240 个外部中断&#xff0c;并且具有 256 级的可编程中断设置。然而芯片厂商一般不会把内核的这些…

《MySQL基础:了解MySQL周边概念》

1.登录选项的认识 -h&#xff1a;指明登录部署了mysql服务的主机&#xff0c;默认为127.0.0.1-P&#xff1a;指明要访问的端口号&#xff0c;默认为3306-u&#xff1a;指明登录用户-p&#xff1a;指明登录密码 2.什么是数据库 2.1认识数据库 第一点理解。 mysql是数据库的客户…

RCL谐振电压增益曲线

谐振电路如何通过调频实现稳压&#xff1f; 为什么要做谐振&#xff1f; 在谐振状态实现ZVS导通&#xff0c;小电流关断 电压增益GVo/Vin&#xff0c;相当于产出投入比 当ff0时&#xff0c;G1时&#xff0c;输出电压输入电压 当G<1时&#xff0c;输出电压<输入电压 …

JavaScript:表单及正则表达式验证

今天我要介绍的是在JavaScript中关于表单验证内容的知识点介绍&#xff1a; 关于表单验证&#xff0c;我接下来则直接将内容以及效果显示出来并作注解&#xff0c;这样可以清晰看见这个表达验证的妙用&#xff1a; <form id"ff" action"https://www.baidu.…

一、Appium环境安装

找了一圈操作手机的工具或软件&#xff0c;踩了好多坑&#xff0c;最后决定用这个工具(影刀RPA手机用的也是这个)&#xff0c;目前最新的版本是v2.17.1&#xff0c;是基于nodejs环境的&#xff0c;有两种方式&#xff0c;我只试了第一种方式&#xff0c;第二种方式应该是比较简…

【c++深入系列】:new和delete运算符详解

&#x1f525; 本文专栏&#xff1a;c &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; “生活不会向你许诺什么&#xff0c;尤其不会向你许诺成功。它只会给你挣扎、痛苦和煎熬的过程。但只要你坚持下去&#xff0c;终有一天&…

正弦波有效值和平均值(学习笔记)

一个周期的正弦波在坐标轴上围的面积有多大&#xff1f; 一般正弦波以 y Asin(wx)表示&#xff0c;其中A为振幅&#xff0c;W为角速度。周期T 2π/w; 确定积分区间是x 0&#xff0c;到x 2π。 计算绝对值积分&#xff1a; 变量代还&#xff1a;wx θ&#xff0c;dx dθ…

第八天 开始Unity Shader的学习之Blinn-Phong光照模型

Unity Shader的学习笔记 第八天 开始Unity Shader的学习之Blinn-Phong光照模型 文章目录 Unity Shader的学习笔记前言一、Blinn-Phong光照模型①计算高光反射部分效果展示 二、召唤神龙:使用Unity内置的函数总结 前言 今天我们编写另一种高光反射的实现方法 – Blinn光照模型…

豆瓣图书数据采集与可视化分析

文章目录 一、适用题目二、豆瓣图书数据采集1. 图书分类采集2. 爬取不同分类的图书数据3. 各个分类数据整合 三、豆瓣图书数据清洗四、数据分析五、数据可视化1. 数据可视化大屏展示 源码获取看下方名片 一、适用题目 基于Python的豆瓣图书数据采集与分析基于Python的豆瓣图书…

常见的爬虫算法

1.base64加密 base64是什么 Base64编码&#xff0c;是由64个字符组成编码集&#xff1a;26个大写字母AZ&#xff0c;26个小写字母az&#xff0c;10个数字0~9&#xff0c;符号“”与符号“/”。Base64编码的基本思路是将原始数据的三个字节拆分转化为四个字节&#xff0c;然后…

YOLOV8 OBB 海思3516训练流程

YOLOV8 OBB 海思3516训练流程 目录 1、 下载带GPU版本的torch(可选) 1 2、 安装 ultralytics 2 3、 下载pycharm 社区版 2 4、安装pycharm 3 5、新建pycharm 工程 3 6、 添加conda 环境 4 7、 训练代码 5 9、配置Ymal 文件 6 10、修改网络结构 9 11、运行train.py 开始训练模…