【Java后端】Spring Boot 全局异常处理最佳实践

Spring Boot 全局异常处理最佳实践

在日常开发中,异常处理几乎是绕不过去的一个话题。尤其在 后端 API 项目 中,如果没有统一的异常处理机制,很容易出现以下问题:

  • Controller 层代码里充斥着 try-catch,显得冗余。
  • 前端拿到的错误响应格式不一致,增加解析成本。
  • 系统异常与业务异常混杂,难以追踪和排查。

因此,在 Spring Boot 项目中,我们通常会通过 全局异常处理 来收敛所有错误,保证接口返回结构的统一性,并简化开发。


一、为什么需要全局异常处理?

在没有全局异常处理之前,开发者常常这样写代码:

@GetMapping("/{id}")
public User getUser(@PathVariable int id) {try {return userService.findById(id);} catch (Exception e) {e.printStackTrace();return null;}
}

问题在于:

  1. try-catch 逻辑冗余,重复代码太多。
  2. 一旦抛出异常,返回值不明确,前端无法准确感知错误信息。

👉 解决方案就是使用 Spring Boot 提供的全局异常处理机制@ControllerAdvice + @ExceptionHandler


二、定义统一返回结果

首先定义一个通用的响应对象 ApiResponse,用于统一接口返回格式:

public class ApiResponse<T> {private int code;private String message;private T data;public ApiResponse(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}public static <T> ApiResponse<T> success(T data) {return new ApiResponse<>(200, "success", data);}public static <T> ApiResponse<T> error(int code, String message) {return new ApiResponse<>(code, message, null);}// getter & setter
}

这样一来,无论成功还是失败,都能保证返回结果的结构一致。


三、自定义业务异常

除了系统异常(NullPointerExceptionSQLException 等),我们还需要定义 业务异常,用于明确业务逻辑错误:

public class BusinessException extends RuntimeException {private int code;public BusinessException(int code, String message) {super(message);this.code = code;}public int getCode() {return code;}
}

例如:用户不存在、余额不足、参数非法等,都可以通过 BusinessException 来抛出。


四、编写全局异常处理器

接下来,通过 @RestControllerAdvice 来统一捕获异常:

@RestControllerAdvice
public class GlobalExceptionHandler {// 处理业务异常@ExceptionHandler(BusinessException.class)public ApiResponse<?> handleBusinessException(BusinessException ex) {return ApiResponse.error(ex.getCode(), ex.getMessage());}// 处理参数校验异常@ExceptionHandler(MethodArgumentNotValidException.class)public ApiResponse<?> handleValidException(MethodArgumentNotValidException ex) {String msg = ex.getBindingResult().getFieldError().getDefaultMessage();return ApiResponse.error(400, msg);}// 兜底异常处理@ExceptionHandler(Exception.class)public ApiResponse<?> handleException(Exception ex) {ex.printStackTrace(); // 可接入日志系统return ApiResponse.error(500, "服务器内部错误");}
}

这样,不管是业务异常还是系统异常,都会走到全局处理器,保证返回结果的统一性。


五、使用示例

Controller

@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/{id}")public ApiResponse<String> getUser(@PathVariable int id) {if (id == 0) {throw new BusinessException(404, "用户不存在");}return ApiResponse.success("用户ID: " + id);}
}

请求与响应

  • 正常请求:
{"code": 200,"message": "success","data": "用户ID: 1"
}
  • 业务异常:
{"code": 404,"message": "用户不存在","data": null
}
  • 系统异常:
{"code": 500,"message": "服务器内部错误","data": null
}

六、总结

通过 全局异常处理,我们实现了以下目标:

  1. 统一返回结构,方便前端解析。
  2. 集中管理异常,减少冗余 try-catch
  3. 区分业务异常与系统异常,提升代码可维护性。
  4. 可扩展性强,后续可以接入日志系统(如 Logback、ELK)或异常监控平台(如 Sentry)。

建议在实际项目中,将 全局异常处理 作为基础框架的一部分,避免每个 Controller 重复造轮子。


七、日志落库 / ELK 接入最佳实践

在真实的生产环境中,仅仅返回统一的错误信息还不够。我们还需要对异常进行持久化存储与分析,以便后续排查问题和改进系统。

常见的做法有两种:

1. 日志落库(数据库存储)

在全局异常处理器中,可以将异常信息写入数据库(例如 MySQL、PostgreSQL):

@ExceptionHandler(Exception.class)
public ApiResponse<?> handleException(Exception ex, HttpServletRequest request) {// 记录日志信息ErrorLog log = new ErrorLog();log.setUri(request.getRequestURI());log.setMethod(request.getMethod());log.setMessage(ex.getMessage());log.setStackTrace(Arrays.toString(ex.getStackTrace()));log.setCreateTime(LocalDateTime.now());errorLogRepository.save(log); // JPA 或 MyBatis 保存return ApiResponse.error(500, "服务器内部错误");
}

其中 ErrorLog 可以定义为:

@Entity
public class ErrorLog {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String uri;private String method;private String message;private String stackTrace;private LocalDateTime createTime;// getter & setter
}

这样就能将异常详细信息落到数据库,方便后续查询与统计。


2. 接入 ELK(Elasticsearch + Logstash + Kibana)

如果系统日志量较大,推荐接入 ELK,实现实时日志收集与可视化。

(1)配置 Logback 输出 JSON 日志

logback-spring.xml 中使用 logstash-logback-encoder 输出 JSON:

<configuration><appender name="LOGSTASH" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/app-log.json</file><encoder class="net.logstash.logback.encoder.LoggingEventEncoder"/></appender><root level="INFO"><appender-ref ref="LOGSTASH"/></root>
</configuration>
(2)通过 Logstash 收集日志

配置 Logstash(logstash.conf):

input {file {path => "/usr/local/app/logs/app-log.json"codec => json}
}output {elasticsearch {hosts => ["http://localhost:9200"]index => "springboot-error-%{+YYYY.MM.dd}"}
}
(3)在 Kibana 可视化

通过 Kibana Dashboard,可以实现:

  • 错误趋势图
  • 按 API 维度统计异常数量
  • 按时间维度分析错误高峰
  • 搜索具体异常堆栈

3. 最佳实践建议

  • 开发环境:控制台打印异常即可,方便调试。
  • 测试环境:日志落库,方便 QA 排查问题。
  • 生产环境:接入 ELK,实时收集和可视化,必要时配合 告警系统(如飞书/钉钉机器人、Prometheus Alertmanager)

八、飞书机器人告警接入

在生产环境中,仅仅记录日志还不够。当发生严重异常时,我们希望能 实时收到告警通知,避免问题被埋没。常见的方式就是接入 企业 IM 工具(如飞书、钉钉、企业微信)。

下面以 飞书自定义机器人 为例,展示如何在全局异常处理器中推送告警。


1. 创建飞书自定义机器人

  1. 打开飞书群聊 → 设置 → 群机器人 → 添加机器人 → 选择 自定义机器人
  2. 复制生成的 Webhook 地址,类似:
https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx

2. 编写工具类(推送异常消息)

使用 RestTemplate 发送 POST 请求:

@Component
public class FeishuBotNotifier {private static final String WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx";private final RestTemplate restTemplate = new RestTemplate();public void sendAlert(String title, String content) {Map<String, Object> body = new HashMap<>();body.put("msg_type", "text");Map<String, String> text = new HashMap<>();text.put("text", String.format("【异常告警】\n标题: %s\n详情: %s", title, content));body.put("content", text);restTemplate.postForEntity(WEBHOOK_URL, body, String.class);}
}

3. 在全局异常处理器中调用

当捕获到异常时,推送到飞书:

@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {private final FeishuBotNotifier feishuBotNotifier;@ExceptionHandler(Exception.class)public ApiResponse<?> handleException(Exception ex, HttpServletRequest request) {// 构造告警信息String title = "SpringBoot 服务异常";String content = String.format("URI: %s\nMethod: %s\n错误: %s",request.getRequestURI(), request.getMethod(), ex.getMessage());// 发送飞书告警feishuBotNotifier.sendAlert(title, content);ex.printStackTrace();return ApiResponse.error(500, "服务器内部错误");}
}

4. 飞书群内效果

触发异常后,群内会收到类似消息:

【异常告警】
标题: SpringBoot 服务异常
详情: 
URI: /user/0
Method: GET
错误: 用户不存在

九、总结(终极版 🚀)

到这里,我们的 全局异常管理方案 已经形成了一整套闭环:

  1. 全局异常处理:统一返回格式,简化开发。
  2. 业务异常区分:明确业务错误与系统错误。
  3. 日志落库:异常可持久化追溯。
  4. ELK 接入:日志可视化分析,支持查询与报表。
  5. 飞书机器人告警:重大异常实时通知,提高运维响应速度。

👉 这样一套机制,基本涵盖了 从接口开发 → 异常收敛 → 日志分析 → 实时告警 的完整链路,既保证了系统的可维护性,也提升了线上运维的响应效率。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93702.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

K8S-Configmap资源

目录 一、核心概念​ ​定义​ ​核心价值​ ​与Secret的区别​ ​二、核心特性​ ​数据存储​ ​生命周期​ ​作用域​ 什么是 Configmap&#xff1f; Configmap 能解决哪些问题&#xff1f; ConfigMap 的主要作用 三、命令行直接创建 四、通过文件创建&#xf…

MySQL InnoDB事务acid特性的原理和隔离级别的实现原理

InnoDB存储引擎 InnoDB存储结构表空间 则每张表都会有一个表空间&#xff08;xxx.ibd&#xff09;&#xff0c;一个mysql实例可以对应多个表空间 系统表空间 存储数据字典&#xff08;表结构定义、索引信息等&#xff09;、Change Buffer、Doublewrite Bufferundo log&#xff…

Linux系统之部署nullboard任务管理工具

Linux系统之部署nullboard任务管理工具一、nullboard介绍1.1 nullboard简介1.2 任务看板工具介绍1.3 nullboard使用场景二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、安装httpd软件3.1 检查yum仓库3.2 安装httpd软件3.3 启动httpd服务3.4 查看httpd服务状态3.5 防火墙…

Qt设置软件使用期限【新版防修改系统时间】

在工业软件或其他领域中&#xff0c;经常会对软件进行授权&#xff0c;软件需要付费进行有期限的使用。以下是我用Qt设计的设置软件使用期限的两种方案。 主体思想&#xff1a; 1.软件需要绑定机器&#xff0c;让用户无法通过复制在另一台机器上运行。 2.由厂家提供激活码供用户…

【JavaEE】多线程(线程安全问题)

有些代码在单个线程环境下执行正确&#xff0c;如果同样的代码在多个线程下同时执行可能就会出现问题&#xff0c;这个就是线程安全问题&#xff08;或者称线程不安全问题&#xff09;&#xff0c;简而言之就是&#xff1a;线程安全问题是由于多线程出现的问题&#xff0c;原因…

NodeJs 桌面开发学习 electron.js (一)

今天开始学习NodeJs 关于 桌面应用的内容&#xff0c;长期目标是 React electron 实现一个桌面应用。今天先实现一个简单的目标&#xff0c;搭建一个Electron ts 项目架构&#xff0c;并实现主业务线程 和前端渲染线程的交互一、代码结构和配置例子项目结构大致如下&#xff…

diffusion model(1.4) 相关论文阅读清单

以下是阅读清单&#xff1a; 《Deep Unsupervised Learning using Nonequilibrium Thermodynamics》扩散模型&#xff0c;arxiv链接《Denoising Diffusion Probabilistic Models》DDPM论文 arxiv链接

ESP32-C3_SMARTCAR

前言: 前面用stm32f103c8t6 rt-thread 写了个智能小车程序 这章用esp32-c3 重新来遍 1&#xff1a;环境 vscodeidf5.4 esp32-3c 找到一块MIN的底板 凑合用&#xff08;138 cm左右&#xff09; 一个L298N 一个船型开关&#xff0c; 一个665mm 2脚按钮 锂电池 186502 及电池盒&a…

消费者API

目录独立消费者案例&#xff08;订阅主题&#xff09;独立消费者案例&#xff08;订阅分区&#xff09;消费者组案例独立消费者案例&#xff08;订阅主题&#xff09; package com.tsg.kafka.consumer;import org.apache.kafka.clients.consumer.ConsumerConfig; import org.ap…

C# NX二次开发:操作按钮控件Button和标签控件Label详解

大家好&#xff0c;今天介绍ug二次开发过程中的一个叫操作按钮的控件&#xff0c;这个控件在块UI编辑器中可以使用。 ​ Button这个控件的属性和方法如下所示&#xff1a; namespace NXOpen.BlockStyler { public class Label : UIBlock { protected intern…

Vue.prototype 的作用

在 Vue.js 中&#xff0c;Vue.prototype 是用来向所有 Vue 实例添加属性或方法的机制。通过它添加的属性或方法可以在所有 Vue 组件实例中通过 this 访问。主要作用添加全局方法或属性&#xff1a;可以在所有组件中使用的工具方法或常量扩展 Vue 功能&#xff1a;添加 Vue 本身…

Javaee 多线程 --进程和线程之间的区别和联系

文章目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnable(接口)&#xff0c;重写run继承Thread,重写run,但是使用匿名内部类实现Runnable(接口)&#xff0c;重写run&#xff0c;但是使用匿名内部类使用lambda表达式请说明Thread类中run和…

企业如何让内部视频仅限指定域名播放,确保视频不被泄露?

在数字化办公时代&#xff0c;企业内部的培训视频、产品演示或机密会议录像等敏感内容&#xff0c;一旦被非法传播或泄露&#xff0c;可能带来严重的商业风险。如何确保这些视频只能在公司官网或指定域名播放&#xff0c;防止被恶意下载、盗链或二次传播&#xff1f;今天介绍一…

端口映射原理操作详解教程:实现外网访问内网服务,本地路由器端口映射公网ip和软件端口映射域名2种方法

端口映射作为一种不同网络间通信的关键网络技术&#xff0c;在远程访问和内外网连接服务需求日益增长的如今&#xff0c;理解端口映射的原理和设置方法是确保网络服务可用性的必要技能。本文将深入探讨端口映射的基本概念、路由器端口映射设置步骤以及无公网IP用端口映射软件映…

【PyTorch】多对象分割项目

对象分割任务的目标是找到图像中目标对象的边界。实际应用例如自动驾驶汽车和医学成像分析。这里将使用PyTorch开发一个深度学习模型来完成多对象分割任务。多对象分割的主要目标是自动勾勒出图像中多个目标对象的边界。 对象的边界通常由与图像大小相同的分割掩码定义&#xf…

SSH 使用密钥登录服务器

用这种方法远程登陆服务器的时候无需手动输入密码 具体步骤 客户端通过 ssh-keygen 生成公钥和私钥 ssh-keygen -t rsa 生成的时候会有一系列问题&#xff0c;根据自己的需要选择就行。生成的结果为两个文件&#xff1a; 上传公钥至服务器&#xff0c;上述两个文件一般在客户…

MySQL 8.4 企业版启用TDE功能和表加密

一、系统环境操作系统&#xff1a;Ubuntu 24.04 数据库:8.4.4-commercial for Linux on x86_64 (MySQL Enterprise Server - Commercial)二、安装TDE组件前提&#xff1a;检查组件文件是否存在ls /usr/lib/mysql/plugin/component_keyring_encrypted_file.so1.配置全局清单文件…

【Altium designer】导出的原理图PDF乱码异常的解决方法

一、有些电源名字无法显示或器件丢失 解决办法 (1)首先AD18以及以上的新版本AD不存在该问题。 (2)其次AD17以及更旧版本的AD很可能遇到该问题,参考如下博客笔记进行操作即可: 大致的操作如下:DXP → Preferences → Schematic → Options里面“Render Text with GDI+”…

4.Ansible自动化之-部署文件到主机

4 - 部署文件到受管主机 实验环境 先通过以下命令搭建基础环境&#xff08;创建工作目录、配置 Ansible 环境和主机清单&#xff09;&#xff1a; # 在控制节点&#xff08;controller&#xff09;上创建web目录并进入&#xff0c;作为工作目录 [bqcontroller ~]$ mkdir web &a…

Vuex的使用

Vuex 超详细使用教程&#xff08;从入门到精通&#xff09;一、Vuex 是什么&#xff1f;Vuex 是专门为 Vue.js 设计的状态管理库&#xff0c;它采用集中式存储管理应用的所有组件的状态。简单来说&#xff0c;Vuex 就是一个"全局变量仓库"&#xff0c;所有组件都可以…