Spring Boot 3 统一响应与全局异常处理实战

前言

在前后端分离的项目中,接口返回格式统一、异常处理规范,是非常基础但极其重要的一环。

如果每个 Controller 都自己 try-catch,或者返回格式五花八门,前端处理起来会非常痛苦。比较好的做法是:

  • 统一响应结构:code + message + data
  • 自定义业务异常:BusinessException
  • 全局异常处理:@RestControllerAdvice
  • 参数校验异常统一拦截

本文基于 Spring Boot 3 + JDK 17,手把手实现一套通用方案,代码可直接复制使用。


一、项目环境

  • JDK 17
  • Spring Boot 3.2.x
  • Maven
  • Lombok(可选,本文使用,简化代码)

二、引入依赖

xml 复制代码
<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>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

三、定义统一响应结果

创建 Result<T> 类:

java 复制代码
package com.example.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {

    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        return new Result<>(
                ResultCode.SUCCESS.getCode(),
                ResultCode.SUCCESS.getMessage(),
                data
        );
    }

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> error(ResultCode resultCode) {
        return new Result<>(
                resultCode.getCode(),
                resultCode.getMessage(),
                null
        );
    }

    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null);
    }
}

四、定义状态码枚举

创建 ResultCode:

java 复制代码
package com.example.common;

import lombok.Getter;

@Getter
public enum ResultCode {

    SUCCESS(200, "操作成功"),
    FAILED(500, "操作失败"),
    VALIDATE_FAILED(400, "参数校验失败"),
    UNAUTHORIZED(401, "暂未登录或 token 已过期"),
    FORBIDDEN(403, "没有相关权限");

    private final Integer code;
    private final String message;

    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

五、自定义业务异常

创建 BusinessException:

java 复制代码
package com.example.common.exception;

import com.example.common.ResultCode;
import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {

    private final ResultCode resultCode;

    public BusinessException(ResultCode resultCode) {
        super(resultCode.getMessage());
        this.resultCode = resultCode;
    }
}

业务代码中可以直接抛出:

java 复制代码
throw new BusinessException(ResultCode.VALIDATE_FAILED);

六、全局异常处理器

创建 GlobalExceptionHandler:

java 复制代码
package com.example.common.exception;

import com.example.common.Result;
import com.example.common.ResultCode;
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;

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理自定义业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e) {
        return Result.error(e.getResultCode());
    }

    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidException(MethodArgumentNotValidException e) {
        FieldError fieldError = e.getBindingResult().getFieldError();
        String message = fieldError != null
                ? fieldError.getDefaultMessage()
                : ResultCode.VALIDATE_FAILED.getMessage();

        return Result.error(ResultCode.VALIDATE_FAILED.getCode(), message);
    }

    /**
     * 兜底异常处理
     */
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        // 生产环境建议在这里记录日志
        return Result.error(ResultCode.FAILED);
    }
}

七、测试 Controller

创建 UserDTO:

java 复制代码
package com.example.controller;

import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class UserDTO {

    @NotBlank(message = "用户名不能为空")
    private String username;
}

创建 UserVO:

java 复制代码
package com.example.controller;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class UserVO {

    private Long id;
    private String username;
}

创建 UserController:

java 复制代码
package com.example.controller;

import com.example.common.Result;
import com.example.common.ResultCode;
import com.example.common.exception.BusinessException;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/{id}")
    public Result<UserVO> getUser(@PathVariable Long id) {
        if (id <= 0) {
            throw new BusinessException(ResultCode.VALIDATE_FAILED);
        }
        return Result.success(new UserVO(id, "张三"));
    }

    @PostMapping
    public Result<Void> create(@RequestBody @Valid UserDTO userDTO) {
        // 模拟业务处理
        return Result.success();
    }
}

八、测试效果

1. 正常请求

http 复制代码
GET /user/1

返回:

json 复制代码
{
  "code": 200,
  "message": "操作成功",
  "data": {
    "id": 1,
    "username": "张三"
  }
}

2. 业务异常

http 复制代码
GET /user/0

返回:

json 复制代码
{
  "code": 400,
  "message": "参数校验失败",
  "data": null
}

3. 参数校验异常

http 复制代码
POST /user
Content-Type: application/json

{
  "username": ""
}

返回:

json 复制代码
{
  "code": 400,
  "message": "用户名不能为空",
  "data": null
}

4. 系统异常

如果代码抛出未捕获异常,会统一返回:

json 复制代码
{
  "code": 500,
  "message": "操作失败",
  "data": null
}

九、总结

这套方案的核心就三点:

  1. 统一响应结构 :前端只需要判断 code,处理逻辑更简单。
  2. 自定义业务异常:业务错误主动抛出,代码更清晰。
  3. 全局异常处理 :所有异常集中拦截,避免到处写 try-catch。

在实际项目中,还可以继续扩展:

  • 自定义错误码枚举
  • 异常日志记录
  • 链路追踪 traceId
  • 国际化消息
  • 对不同异常返回不同 HTTP 状态码
相关推荐
余槐i1 小时前
将AI Agent嵌入现有Java系统时,Spring Boot 3.2的异步冲突与内存泄漏排查
人工智能·spring boot·性能优化·kubernetes·ai agent
JavaGuide1 小时前
DeepSeek Harness 官方桌面端终于有了!
前端·后端
一条破秋裤1 小时前
Linux 线程分离与主动取消:pthread_detach、pthread_cancel
java·linux·jvm
SFLYQ1 小时前
隔离内网下 AI Agent 工程实战
后端·agent·ai编程
茉莉玫瑰花茶1 小时前
OpenGL [ 基础概念 ]
java·前端·数据库
周杰偷奶茶1 小时前
【Java】运算符指南
java·开发语言
随性而行3601 小时前
企业微信二次开发如何接入大模型工具?API接口实现智能任务调用的技术思路
java·前端·人工智能·python·微信·机器人·企业微信
凤山老林1 小时前
Spring Boot 集成 Netty 构建高性能 TCP 长连接网关:协议解析、心跳检测与集群广播实战
spring boot·后端·tcp/ip