Spring Boot 参数校验工作流:DTO、注解和统一错误响应
Java 后端接口最容易混乱的地方之一,是参数校验散落在控制器、服务层和数据库异常里。短期看只是多写几个 if,长期会变成错误信息不一致、前端无法定位字段、测试用例难覆盖。
本文把 Spring Boot 参数校验整理成一个完整工作流:先明确接口边界,再用 DTO 承接输入,用注解声明规则,用 @Valid 触发校验,最后通过统一异常处理返回稳定的错误结构。
- 目标和边界:校验应该解决什么问题
- 全流程总览:从请求体到错误响应
- 阶段一:用 DTO 明确输入边界
- 阶段二:用注解声明字段规则
- 阶段三:统一错误响应结构
- 推荐流程:从新增接口到回归检查
- 常见误区和速查表
目标和边界:校验应该解决什么问题
参数校验的目标不是把所有业务规则都塞进注解,而是先拦住明显不合法的输入,让控制器收到的数据满足基本形状。例如用户名不能为空、手机号格式不对、页码不能小于 1、金额不能为负数。
建议把规则分成三类:
- 结构规则:字段是否必填、长度范围、数值范围、集合大小。
- 格式规则:邮箱、手机号、日期字符串、枚举值。
- 业务规则:库存是否足够、用户是否有权限、订单状态是否允许变更。
前两类适合放在 DTO 校验里,第三类通常放在服务层。边界清楚后,代码会更稳定。
全流程总览:从请求体到错误响应
一个推荐的参数校验链路可以拆成五步:请求进入控制器、绑定到 DTO、触发注解校验、捕获字段错误、返回统一响应。这样每个接口都沿用同一套入口和错误格式。

阶段一:用 DTO 明确输入边界
不要直接把数据库实体当作请求参数对象。实体通常包含主键、创建时间、内部状态等字段,直接暴露给接口会让输入边界变模糊。推荐为每个写接口准备单独的请求 DTO。
public class CreateUserRequest {
private String username;
private String phone;
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
DTO 的职责是描述外部输入。它不负责保存数据,也不负责判断复杂业务状态。
阶段二:用注解声明字段规则
Spring Boot 常用 Bean Validation 注解声明字段约束。不同版本项目可能使用 jakarta.validation 或 javax.validation 包,原则相同:规则写在 DTO 字段上,由框架在控制器入口触发。
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(max = 32, message = "用户名不能超过32个字符")
private String username;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Min(value = 1, message = "年龄不能小于1")
@Max(value = 120, message = "年龄不能大于120")
private Integer age;
// getter 和 setter 省略
}
控制器入口需要加上 @Valid,否则注解只是一组元数据,不会自动触发校验。
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@PostMapping("/users")
public ApiResult createUser(@Valid @RequestBody CreateUserRequest request) {
Long userId = 1001L;
return ApiResult.ok(userId);
}
}
阶段三:统一错误响应结构
校验失败时,前端最需要知道两个信息:哪个字段错了,以及错误原因是什么。不要只返回一句“参数错误”,否则前端无法高亮具体输入框。

import java.util.List; public class ApiResult{ private String code; private String message; private T data; public static ApiResult ok(T data) { ApiResult result = new ApiResult(); result.code = "OK"; result.message = "success"; result.data = data; return result; } public static ApiResult > badRequest(List
errors) { ApiResult > result = new ApiResult(); result.code = "BAD_REQUEST"; result.message = "参数校验失败"; result.data = errors; return result; } }
public record FieldErrorItem(String field, String message) {
}
统一异常处理可以集中收集字段错误:
import java.util.List;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalErrorHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResult> handleInvalidBody(
MethodArgumentNotValidException ex) {
List errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> new FieldErrorItem(error.getField(), error.getDefaultMessage()))
.toList();
return ApiResult.badRequest(errors);
}
}
返回结构可以保持稳定:
{
"code": "BAD_REQUEST",
"message": "参数校验失败",
"data": [
{
"field": "phone",
"message": "手机号格式不正确"
}
]
}
推荐流程:从新增接口到回归检查
新增一个写接口时,可以按下面的顺序推进:
- 先定义请求 DTO,只放外部允许传入的字段。
- 给必填、长度、范围、格式规则添加注解。
- 控制器入口添加
@Valid和@RequestBody。 - 统一异常处理里返回字段级错误列表。
- 为必填为空、格式错误、边界值写接口测试。
- 把跨用户、权限、库存这类业务规则留在服务层。
常见误区和速查表
误区 1:DTO 和实体混用
实体字段通常比接口输入更多,混用会扩大可写范围。DTO 应该面向接口设计,实体面向存储设计。
误区 2:忘记添加 @Valid
字段上写了注解,但控制器入口没有 @Valid,校验不会按预期触发。排查时先看控制器方法签名。
误区 3:把复杂业务规则写进注解
库存、权限、订单状态这类规则依赖数据库或上下文,放在服务层更清晰。DTO 注解适合处理输入形状和基础格式。
速查表
| 目标 | 常用做法 | 检查点 |
|---|---|---|
| 字段必填 | @NotBlank、@NotNull |
空字符串和 null 都要测试 |
| 长度范围 | @Size |
测试最大长度边界 |
| 数值范围 | @Min、@Max |
测试最小值和最大值 |
| 格式规则 | @Pattern |
错误样例要覆盖常见输入 |
| 错误响应 | 统一异常处理 | 返回字段名和错误消息 |
总结一下,参数校验要形成工作流,而不是散落的条件判断。DTO 控制输入边界,注解声明基础规则,控制器触发校验,统一异常处理稳定错误响应。这样新接口越多,参数校验越容易复用和回归。
MySQL 覆盖索引实验:从慢查询到 EXPLAIN 显示 Using index
- 上一篇
- MySQL 覆盖索引实验:从慢查询到 EXPLAIN 显示 Using index
- 下一篇
- RAG 答非所问怎么排查:从切块、向量到召回上下文
-
- 文章 · java教程 | 1小时前 |
- Java 虚拟线程连接池改造的资源边界
- 236浏览 收藏
-
- 文章 · java教程 | 6小时前 | Java · 虚拟线程 · java UncaughtExceptionHandler 虚拟线程 Thread.Builder.OfVirtual
- Java Thread.Builder.OfVirtual 设置线程异常处理器
- 139浏览 收藏
-
- 文章 · java教程 | 8小时前 | 数据处理 · Java教程 · java windowFixed Stream Gatherer 事件窗口
- Java Stream Gatherer 组合短窗口事件的实现步骤
- 495浏览 收藏
-
- 文章 · java教程 | 17小时前 | 并发 · Java · 随机数 · RandomGeneratorFactory Java随机算法 随机数并发
- Java RandomGeneratorFactory 怎么按能力选择随机算法
- 244浏览 收藏
-
- 文章 · java教程 | 22小时前 |
- Java HexFormat 怎么在字节数组和十六进制文本间转换
- 361浏览 收藏
-
- 文章 · java教程 | 1天前 | 文件处理 · nio · Java教程 · java 文件比较 Files.mismatch 字节偏移
- Java Files.mismatch 怎么定位两个文件首个差异
- 342浏览 收藏
-
- 文章 · java教程 | 1天前 | Java ·
- Java Base64 流式编码怎么避免一次加载大文件
- 182浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · 可观测性 ·
- Java JFR EventStream 怎么实时消费运行事件
- 145浏览 收藏
-
- 文章 · java教程 | 1天前 | Java ·
- Java Class-File API 怎么读取类文件结构
- 419浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · Stream ·
- Java Stream Gatherer 怎么实现有状态中间操作
- 494浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 258次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 302次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 281次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 259次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 67次使用
-
- Go Java 算法之字符串解码示例详解
- 2023-01-07 479浏览
-
- Go Java算法之单词搜索示例详解
- 2022-12-30 337浏览
-
- Gojava算法之括号生成示例详解
- 2023-02-22 128浏览
-
- GoJava算法之累加数示例详解
- 2023-01-07 149浏览
-
- GoJava算法最大单词长度乘积示例详解
- 2023-01-12 202浏览

