跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

SpringBoot 统一数据返回与异常处理详解

SpringBoot 项目中通过 ControllerAdvice 结合 ResponseBodyAdvice 实现统一数据封装,解决前端解析一致性问题。针对 String 类型返回值导致的消息转换器冲突,采用 ObjectMapper 进行序列化适配。同时利用 @ExceptionHandler 集中捕获异常,避免敏感信息泄露,提升系统健壮性与前后端协作效率。

XiaoPingzi发布于 2026/3/16更新于 2026/7/2032 浏览
SpringBoot 统一数据返回与异常处理详解

SpringBoot 统一数据返回与异常处理详解

在开发过程中,前后端交互的数据格式往往需要标准化。通过 SpringBoot 提供的扩展点,我们可以轻松实现统一的响应封装和全局异常捕获,减少重复代码并提升系统健壮性。

统一数据返回格式

快速入门

利用 @ControllerAdvice 配合 ResponseBodyAdvice 接口是实现统一响应的经典方案。前者用于定义通知类,后者则负责拦截控制器方法的返回值。

package com.hbu.book.responseAdvice;

import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return false;
    }

    @Override
    public @Nullable Object beforeBodyWrite(@Nullable Object body,
                                            MethodParameter returnType,
                                            MediaType selectedContentType,
                                            Class selectedConverterType,
                                            ServerHttpRequest request,
                                            ServerHttpResponse response) {
        return null;
    }
}

supports 方法决定了是否执行后续的处理逻辑。默认返回 false 表示不生效,我们需要将其改为 true 以启用统一封装。

未配置前,接口直接返回原始数据:

未封装前的接口返回

我们需要一个通用的结果包装类 Result:

package com.hbu.book.model;

import com.hbu.book.enums.ResultCodeEnum;
import lombok.Data;

@Data
public class Result<T> {
    private ResultCodeEnum code; // -1 未登录 200 正常 -2 出错
    private String errMsg;
    private T data;

    public static <T> Result success(T data) {
        Result result = new Result();
        result.setCode(ResultCodeEnum.SUCCESS);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }

    public static <T> Result fail(String errMsg) {
        Result result = new Result();
        result.setCode(ResultCodeEnum.FAIL);
        result.setErrMsg(errMsg);
        result.setData(null);
        return result;
    }

    public static <T> Result fail(String errMsg, T data) {
        Result result = new Result();
        result.setCode(ResultCodeEnum.FAIL);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }

    public static <T> Result unlogin() {
        Result result = new Result();
        result.setCode(ResultCodeEnum.UNLOGIN);
        result.setErrMsg("用户未登录");
        return result;
    }
}

修改 ResponseAdvice 中的 supports 为 true,并在 beforeBodyWrite 中返回封装后的对象:

package com.hbu.book.responseAdvice;

import com.hbu.book.model.Result;
import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public @Nullable Object beforeBodyWrite(@Nullable Object body,
                                            MethodParameter returnType,
                                            MediaType selectedContentType,
                                            Class selectedConverterType,
                                            ServerHttpRequest request,
                                            ServerHttpResponse response) {
        return Result.success(body);
    }
}

再次访问接口,可以看到所有数据都被包裹在了 Result 结构中:

统一封装后的接口返回

存在问题

在实际测试中发现,当控制器返回类型为 String 时,偶尔会出现类型转换错误,尽管数据库中数据已正确写入。

package com.hbu.book.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }

    @RequestMapping("/t2")
    public boolean t2() {
        return true;
    }

    @RequestMapping("/t3")
    public Integer t3() {
        return 200;
    }
}

经排查,核心原因在于 Spring 的消息转换器优先级机制。当方法返回 String 时,Spring 优先使用 StringHttpMessageConverter,但该转换器只处理 String 类型。而我们的 ResponseAdvice 将 String 包装成了 Result 对象,导致转换器尝试将 Result 强制转为 String 时报错。

代码优化

针对上述问题,需要在 beforeBodyWrite 中增加对 String 类型的特殊处理,利用 Jackson 的 ObjectMapper 进行序列化:

package com.hbu.book.config;

import com.hbu.book.model.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import tools.jackson.databind.ObjectMapper;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType, Class selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result) {
            return body;
        }
        if (body instanceof String) {
            try {
                return objectMapper.writeValueAsString(Result.success(body));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return Result.success(body);
    }
}

优点

  1. 前端解析便捷:统一的数据结构降低了前端对接成本。
  2. 规范制定:后端团队可强制执行标准,避免返回内容五花八门。
  3. 维护性高:若需调整状态码或字段名,只需修改一处即可全局生效。

统一异常处理

全局异常处理通常结合 @ControllerAdvice 和 @ExceptionHandler 实现。当发生异常时,框架会自动调用对应的处理方法。

package com.hbu.book.config;

import com.hbu.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionAdvice {
    @ExceptionHandler
    public Object handler(Exception e) {
        log.error("出现异常:", e);
        return Result.fail(e.getMessage());
    }
}

验证效果:

package com.hbu.book.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        Integer x = 7 / 0;
        return "t1";
    }

    @RequestMapping("/t2")
    public boolean t2() {
        String a = null;
        a.contains("a");
        return true;
    }

    @RequestMapping("/t3")
    public Integer t3() {
        int[] arr = new int[10];
        System.out.println(arr[100]);
        return 200;
    }
}

除零异常处理

空指针异常处理

数组越界异常处理

针对不同异常,可以定义多个处理器方法,或者指定具体的异常类型:

package com.hbu.book.config;

import com.hbu.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionAdvice {
    @ExceptionHandler
    public Object handler(Exception e) {
        log.error("出现异常:", e);
        return Result.fail(e.getMessage());
    }

    @ExceptionHandler(NullPointerException.class)
    public Object handler1(Exception e) {
        log.error("出现空指针异常:", e);
        return Result.fail("系统内部错误");
    }

    @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
    public Object handler2(Exception e) {
        log.error("出现数组越界异常:", e);
        return Result.fail("参数错误");
    }
}

生产环境中,建议不要将具体的异常堆栈信息直接返回给前端,以免泄露敏感细节。如果没有匹配到具体异常的处理器,Spring 会自动向上查找父类的异常处理方法,确保异常最终能被捕获处理。

目录

  1. SpringBoot 统一数据返回与异常处理详解
  2. 统一数据返回格式
  3. 快速入门
  4. 存在问题
  5. 代码优化
  6. 优点
  7. 统一异常处理
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 前端微前端架构:大型项目应用与风险分析
  • GitHub Copilot CLI 斜杠命令速查表
  • 并查集数据结构详解与实战应用
  • Java 数据结构:排序算法解析(一)
  • 动态规划专题:子序列问题深度解析
  • PyApp:将 Python 工程打包为可执行文件的简易方法
  • 使用 DeepSeek 辅助开发高性能贪吃蛇游戏
  • Git cherry-pick 命令详解
  • 基于官方 API 搭建 QQ 群聊机器人实战指南
  • 字节跳动 Linux C/C++ 后端面试真题解析
  • 老手机 本地部署小龙虾OpenClaw(使用本地千问大模型)实机演示 Termux+Ubuntu+Llama 新手完整安装教程(含代码)
  • SkyWalking Python 分布式追踪实战:skywalking-python 埋点指南
  • 支付宝 SDK 集成:深入理解两种支付回调区别
  • 5 款降低 AIGC 检测率工具实测对比与选择建议
  • WebRTC 技术详解
  • LangChain 实战:工具调用与结构化输出
  • 在 Cursor 中配置和使用 MCP 服务
  • 力扣 Hot 100 链表算法题 Python 实现
  • Selenium 自动化中如何获取折叠面板内的内容
  • Ground Slow, Move Fast: 一种通用可泛化的双系统视觉 - 语言导航基础模型

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online