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教程 | 13小时前 | Java教程 · TTL缓存 · ConcurrentHashMap · 小项目 · java 本地缓存 concurrenthashmap TTL缓存 过期淘汰
- Java 本地 TTL 缓存小项目:用 ConcurrentHashMap 实现过期淘汰和命中统计
- 394浏览 收藏
-
- 文章 · java教程 | 17小时前 | Java · Stream · 数据处理 · 后端教程 · Java Stream bigdecimal 分组统计 Collectors 订单汇总
- Java Stream 分组统计实验:从订单列表到客户消费汇总
- 355浏览 收藏
-
- 文章 · java教程 | 1星期前 | map · 并发安全 · 缓存设计 · Java教程 · java optional concurrenthashmap computeIfAbsent Map缓存
- Java computeIfAbsent 缓存初始化实战:少写判断、避开空值和并发坑
- 236浏览 收藏
-
- 文章 · java教程 | 1星期前 | Java · 异步编程 · 后端开发 · CompletableFuture · 接口聚合 · java 结果合并 completablefuture 并行调用 超时兜底
- Java CompletableFuture 多接口聚合完整流程:并行调用、超时兜底和结果合并
- 428浏览 收藏
-
- 文章 · java教程 | 1星期前 | Java · 线程安全 · DateTimeFormatter · 日期处理 · 并发问题 · java 线程安全 日期格式化 threadlocal SimpleDateFormat DateTimeFormatter
- Java SimpleDateFormat 日期偶发错乱怎么办:从共享实例到线程安全一步步排查
- 481浏览 收藏
-
- 文章 · java教程 | 2星期前 | http接口 · httpclient · Java教程 · 接口调试 · 超时处理 · java 接口调用 httpclient 超时控制 状态码 响应体
- Java HttpClient 调接口实战:超时、状态码和响应体这样处理
- 224浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 2588次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 2394次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 2336次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 2549次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 2525次使用
-
- 极客时间优秀课程推荐分享
- 2023-02-23 100浏览
-
- SpringBoot 整合 Mybatis(入门版)
- 2023-02-24 100浏览
-
- MySQL安装使用学习教程,学mysql数据库入门的不二之选
- 2023-02-24 100浏览
-
- 因为我的一个低级错误,生产数据库崩溃了将近半个小时
- 2023-02-24 100浏览
-
- 大家都是如何看开源代码的
- 2023-01-08 101浏览

