
前言做SpringBoot后端接口开发几乎每个项目都会遇到这几个痛点Controller返回格式不统一有的返回String、有的返回Map前端对接成本高异常直接抛出错误堆栈信息返回前端存在安全风险参数校验代码散落在Controller中大量if判断代码冗余维护困难业务异常、参数异常、系统未知异常混杂在一起无法区分日志不好排查。本文搭建一套企业通用的解决方案统一返回结果封装 自定义业务异常 RestControllerAdvice全局异常拦截 Bean Validation参数校验。 整套代码开箱即用适合新项目直接引入也是面试高频实战考点。技术栈SpringBoot2.x / SpringBoot3.xspring-boot-starter-validation一、Maven依赖引入SpringBoot2.3之后validation不再默认集成需要手动引入依赖!-- 参数校验 starter -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency二、统一返回结果封装2.1 响应码枚举 ResultCode统一管理状态码与提示信息避免硬编码字符串便于前后端对接。/** * 统一返回码枚举 */ public enum ResultCode { SUCCESS(200, 操作成功), FAILED(500, 操作失败), VALIDATE_FAILED(400, 参数校验失败), UNAUTHORIZED(401, 未登录或token失效), FORBIDDEN(403, 权限不足), NOT_FOUND(404, 资源不存在); private final Integer code; private final String message; ResultCode(Integer code, String message) { this.code code; this.message message; } public Integer getCode() { return code; } public String getMessage() { return message; } }2.2 通用返回实体 Result泛型封装支持返回任意类型数据成功、失败静态方法快速构建返回对象。/** * 全局统一返回结果封装 */ public class ResultT { private Integer code; private String msg; private T data; public Result(){} public Result(Integer code, String msg, T data) { this.code code; this.msg msg; this.data data; } // 成功返回带数据 public static T ResultT success(T data){ return new Result(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); } // 成功返回不带数据 public static T ResultT success(){ return new Result(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null); } // 失败返回自定义枚举 public static T ResultT fail(ResultCode resultCode){ return new Result(resultCode.getCode(), resultCode.getMessage(), null); } // 失败返回自定义提示信息 public static T ResultT fail(Integer code, String msg){ return new Result(code, msg, null); } // getter setter public Integer getCode() { return code; } public void setCode(Integer code) { this.code code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg msg; } public T getData() { return data; } public void setData(T data) { this.data data; } }接口返回格式示例{ code: 200, msg: 操作成功, data: { id: 1, username: test } }三、自定义业务异常 BusinessException业务场景主动抛出异常例如用户不存在、余额不足交给全局异常处理器统一捕获Controller减少大量try-catch。/** * 自定义业务异常 */ public class BusinessException extends RuntimeException{ private Integer code; public BusinessException(Integer code, String message){ super(message); this.code code; } public BusinessException(ResultCode resultCode){ super(resultCode.getMessage()); this.code resultCode.getCode(); } public Integer getCode() { return code; } }使用示例在Service/Controller中抛出// 业务逻辑判断如果用户不存在抛出异常 if(user null){ throw new BusinessException(ResultCode.NOT_FOUND.getCode(), 用户不存在); }四、全局异常处理器 GlobalExceptionHandler核心注解RestControllerAdviceExceptionHandlerRestControllerAdvice对所有RestController增强自动返回JSONExceptionHandler捕获指定类型异常编写处理逻辑分层捕获参数校验异常、自定义业务异常、兜底系统异常。import lombok.extern.slf4j.Slf4j; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.stream.Collectors; Slf4j RestControllerAdvice public class GlobalExceptionHandler { /** * 捕获自定义业务异常 */ ExceptionHandler(BusinessException.class) public Result? handleBusinessException(BusinessException e){ log.error(业务异常{}, e.getMessage(), e); return Result.fail(e.getCode(), e.getMessage()); } /** * RequestBody JSON参数校验异常MethodArgumentNotValidException */ ExceptionHandler(MethodArgumentNotValidException.class) public Result? handleValidException(MethodArgumentNotValidException e){ log.error(参数校验异常{}, e.getMessage(), e); String msg e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining()); return Result.fail(ResultCode.VALIDATE_FAILED.getCode(), msg); } /** * 表单提交参数校验异常 BindException */ ExceptionHandler(BindException.class) public Result? handleBindException(BindException e){ log.error(表单参数校验异常{}, e.getMessage(), e); String msg e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining()); return Result.fail(ResultCode.VALIDATE_FAILED.getCode(), msg); } /** * 兜底捕获所有未知异常防止堆栈暴露给前端 */ ExceptionHandler(Exception.class) public Result? handleGlobalException(Exception e){ log.error(系统未知异常, e); // 前端只返回通用提示详细堆栈留在服务端日志 return Result.fail(ResultCode.FAILED.getCode(), 服务器内部异常请联系管理员); } }重点说明1、区分两种参数校验异常MethodArgumentNotValidExceptionRequestBodyJSON请求体校验失败BindException表单提交、url参数对象绑定校验失败2、未知异常只打印完整日志前端屏蔽堆栈信息保障安全3、异常捕获顺序精确异常优先最后兜底Exception。五、Bean Validation 参数校验实战5.1 常用校验注解注解作用NotBlank字符串非空不能为null、空白字符串NotNull不能为null可以是空字符串NotEmpty集合/数组不能为null且长度0Min/Max数字最小/最大值Email邮箱格式校验Pattern正则表达式校验5.2 编写DTO添加校验注解import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; public class UserDTO { NotBlank(message 用户名不能为空) Size(min 2, max 10, message 用户名长度2~10位) private String username; NotBlank(message 密码不能为空) private String password; // getter setter public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getPassword() { return password; } public void setPassword(String password) { this.password password; } }5.3 Controller 使用 Valid 开启校验Valid用于校验请求体DTO Validated支持分组校验可放在类上校验普通请求参数。import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; RestController RequestMapping(/user) public class UserController { PostMapping(/add) public Result? addUser(Valid RequestBody UserDTO userDTO){ // 校验不通过自动抛出MethodArgumentNotValidException全局异常捕获 // 业务逻辑 return Result.success(新增用户成功); } }校验失败返回示例{ code: 400, msg: 用户名不能为空密码不能为空, data: null }六、测试验证正常请求返回code:200成功结构参数缺失触发校验注解返回400提示参数错误业务异常代码抛出BusinessException返回对应业务错误码系统异常空指针等前端只提示服务器异常完整堆栈打印在后端日志。七、高频踩坑点 生产开发规范踩坑清单Valid 和 Validated区别Validjavax提供用于RequestBody不支持分组ValidatedSpring封装支持分组校验写在Controller类上校验普通参数。不要在全局异常中直接返回Exception堆栈给前端信息泄露参数校验如果不写Valid校验注解不会生效SpringBoot3使用jakarta.validation包不再是javax.validationNotBlank只作用字符串Integer类型判空要用NotNull全局异常处理器扫描范围默认扫描当前包及子包放在主启动类同级目录即可。开发规范所有REST接口统一返回ResultT禁止直接返回原始对象、Map业务错误统一抛自定义BusinessException减少Controller try-catch参数校验优先使用Bean Validation注解不要手写大量if判断异常日志打印完整堆栈便于排查前端展示友好简短提示状态码统一维护在枚举禁止在代码写硬编码数字。八、高频问答Q1RestControllerAdvice原理能拦截哪些异常A底层基于AOP对所有RestController做增强。可以拦截Controller层抛出的异常过滤器Filter抛出的异常ControllerAdvice无法捕获。Q2Valid和Validated区别AValid是Java原生用于RequestBody校验不支持分组Validated由Spring提供支持分组校验可以用于方法参数校验。Q3MethodArgumentNotValidException和BindException区别AMethodArgumentNotValidExceptionRequestBodyJSON请求体校验失败BindException表单提交、url参数绑定对象校验失败。Q4为什么要封装统一返回结果A前后端接口格式标准化前端统一解析统一异常返回结构减少重复代码便于接口文档生成。结语这套统一返回全局异常参数校验是企业后端项目基础骨架几乎所有SpringBoot项目都会搭建。 把异常处理、参数校验从业务代码剥离Controller只专注业务逻辑代码干净易维护。同时这套知识点面试经常会结合代码手写提问建议理解原理并把代码动手跑一遍。如果本文对你有帮助欢迎点赞收藏后续继续更新SpringBoot系列实战文章。