跳到主要内容

打造异常处理的优雅之道

异常的定义

在Java中,异常是程序执行过程中发生的不正常情况,它打断了正常的指令流。Java提供了一套完整的异常处理框架,帮助开发者有效地处理运行时出现的错误和异常情况,以确保程序的健壮性和稳定性。Java的异常处理机制是建立在Throwable类及其子类的基础上的。

Throwable

Throwable是Java异常层次结构的根类,它是所有错误和异常的基类。在Java中,只有Throwable的实例或其子类的实例才可以被throw关键字抛出,或者被catch关键字捕获。Throwable有两个主要的子类:ErrorException

  • Error:代表编译时和系统错误(外部错误),如内存溢出(OutOfMemoryError)、虚拟机错误(VirtualMachineError)等。这些错误通常是严重的,程序应该尽量避免发生这类错误,一旦发生,程序通常无法处理。
  • Exception:代表程序本身可以处理的异常。它分为两大类:检查型异常(checked exceptions)和非检查型异常(unchecked exceptions,也称为运行时异常)。

Exception

Exception类是所有检查型异常的父类。检查型异常是那些在编译时必须被处理(捕获或声明抛出)的异常。如果方法可能会抛出某个检查型异常,但没有捕获它,那么该方法必须通过throws关键字声明该异常。

检查型异常通常是外部错误,如尝试打开不存在的文件时发生的FileNotFoundException,或网络通信过程中发生的IOException

RuntimeException

RuntimeExceptionException的子类,代表非检查型异常,即运行时异常。运行时异常包括了Java运行时环境可以自动抛出的异常。

与检查型异常不同,运行时异常是由程序逻辑错误引起的,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。编译器不要求强制处理或声明抛出运行时异常,因为它们通常是可以通过程序逻辑来预防的错误。

总结来说,Java的异常处理机制是通过Throwable类及其子类实现的。Error用于表示严重的系统级错误,通常是不可恢复的;Exception用于表示程序可以处理的异常,分为检查型异常和非检查型异常(RuntimeException)。正确理解和使用这些异常类对于编写健壮、可靠的Java程序至关重要。

以上将异常的特点介绍完毕,而在Springboot中开发时,需要考虑出现异常时的处理,已经抛出异常后如何将信息返回给前端,这就需要进行异常的统一处理

异常的统一处理

在Spring Boot项目中配置统一的异常处理是一种最佳实践,它不仅能够提升代码的可维护性和可读性,还能够为最终用户提供更加友好和一致的错误响应。通过实现统一异常处理,开发者可以集中处理应用中的各种异常,避免了在每个控制器或服务中重复编写错误处理逻辑,从而使得代码更加简洁和易于管理。下面我们将详细探讨未配置统一异常处理的坏处,以及配置后的好处

没有配置统一异常处理的坏处

  1. 代码重复:在没有统一异常处理的情况下,开发者可能需要在每个控制器或服务中编写异常处理代码,这会导致大量的重复代码,增加了代码的维护成本。
  2. 错误处理不一致:各个模块独立处理异常可能会导致错误响应的格式不一致,给前端开发和最终用户带来困扰,影响用户体验。
  3. 难以维护和扩展:随着项目规模的扩大,异常处理逻辑分散在各处,当需要修改错误响应格式或处理逻辑时,必须逐个修改,效率低下,容易遗漏。
  4. 调试困难:没有一个集中的异常处理机制,当出现错误时,定位和调试问题变得更加困难,特别是在复杂的应用中。
  5. 安全风险:不恰当的异常处理可能会泄露敏感信息给客户端,比如堆栈跟踪信息,这可能会导致安全漏洞。

配置统一异常处理的好处

  1. 减少代码重复:通过集中处理所有异常,可以显著减少代码重复,使得异常处理逻辑更加集中和一致。
  2. 提高代码可维护性:统一的异常处理机制使得代码更加整洁,易于管理和维护。对异常处理逻辑的修改和扩展可以在一个地方进行,提高了开发效率。
  3. 增强用户体验:统一异常处理允许开发者提供一致且友好的错误响应给用户,无论是API的消费者还是最终用户,都能从中受益。
  4. 便于错误监控和日志记录:集中处理异常使得监控和记录日志变得更加方便,有助于快速发现并解决问题。
  5. 提升应用的安全性:可以通过统一异常处理来过滤和处理敏感信息,防止敏感数据泄露给客户端,提升应用的安全性。

举例

为了能让小伙伴体会到异常统一配置的好处,我们来直接举例说明

执行成功情况

public UserVo getByMobile(UserMobileDto userMobileDto) {
LambdaQueryWrapper<UserMobile> queryWrapper = Wrappers.lambdaQuery(UserMobile.class)
.eq(UserMobile::getMobile, userMobileDto.getMobile());
UserVo userVo = userMobileMapper.selectOne(queryWrapper);
return userVo;
}
{
"code": "0",
"message": "",
"data": {
"id": "8408725077058789376",
"name": "",
"relName": "",
"gender": "1",
"mobile": "132****4769",
"emailStatus": "0",
"email": "",
"relAuthenticationStatus": "0",
"idNumber": "",
"address": ""
}
}

执行失败情况

直接抛出RuntimeException异常来模拟失败情况

public UserVo getByMobile(UserMobileDto userMobileDto) {
throw new RuntimeException("用户为空");
}

使用异常统一处理

{
"code": "-100",
"message": "系统错误,请稍后重试!",
"data": null
}

没有异常统一处理

{
"timestamp": "2024-03-03 15:33:50",
"status": "500",
"error": "Internal Server Error",
"message": "",
"path": "/user/get/mobile"
}

到这里,小伙伴能发现区别了,发现没有处理异常的情况下,返回的数据结构发生了变化。这就是不允许的情况,因为前端分离的情况下,前端调用后端的数据返回的结构无论在成功或者失败的情况,数据结构都必须保持一致,否则前端根本没有办法解析。

付费内容提示

该文档的全部内容仅对「码力全开」项目实战&技术讲解 知识星球用户开放

加入星球,一次获得完整项目资料、全栈技术知识库和长期答疑服务。

100万+字全栈技术知识库深入讲解技术核心、数据库、中间件和分布式等内容
8套热门的实战项目持续更新的企业级项目覆盖高并发、微服务、数据中台 和 AI Agent 等方向
AI 技术知识大模型面试详解覆盖 AI 模型原理、Agent、RAG、MCP、Skills、Harness 等核心知识点
文档 + 视频两种讲解形式既能系统阅读,也能跟随视频理解核心业务

完整项目实战资料

每套项目均包含从 0 到 1 讲解文档核心业务讲解视频

从基础项目到复杂业务场景,项目资料会持续更新。

8 套项目
  • 01Nexus Agent AI 智能体
  • 02Nexus Agent Pro 完全版
  • 03黑马点评Plus
  • 04大麦
  • 05大麦Pro
  • 06大麦AI
  • 07流量切换
  • 08数据中台

加入后还能获得

进入星球后,即可享受上述所有服务,保证不会再有其他隐藏费用。

从学习、面试到项目启动,都可以继续获得支持。

  • 1 对 1 解答项目和技术问题都可以提问
  • 针对性补充没有讲清楚的内容会继续补充
  • 面试与简历指导梳理回答技巧和项目亮点
  • 中间件云环境项目依赖可以直接接入使用
  • 面试后复盘被问住的问题可以继续交流
  • 远程问题解决项目启动问题可协助排查
知识星球二维码

扫码进入知识星球

  1. 打开微信,扫描左侧二维码,加入「码力全开」项目实战&技术讲解 知识星球
  2. 查看星球使用指导,获取完整项目讲解资料索引
解锁全部付费内容
🎁优惠