打造异常处理的优雅之道
异常的定义
在Java中,异常是程序执行过程中发生的不正常情况,它打断了正常的指令流。Java提供了一套完整的异常处理框架,帮助开发者有效地处理运行时出现的错误和异常情况,以确保程序的健壮性和稳定性。Java的异常处理机制是建立在Throwable类及其子类的基础上的。
Throwable
Throwable是Java异常层次结构的根类,它是所有错误和异常的基类。在Java中,只有Throwable的实例或其子类的实例才可以被throw关键字抛出,或者被catch关键字捕获。Throwable有两个主要的子类:Error和Exception。
- Error:代表编译时和系统错误(外部错误),如内存溢出(
OutOfMemoryError)、虚拟机错误(VirtualMachineError)等。这些错误通常是严重的,程序应该尽量避免发生这类错误,一旦发生,程序通常无法处理。 - Exception:代表程序本身可以处理的异常。它分为两大类:检查型异常(checked exceptions)和非检查型异常(unchecked exceptions,也称为运行时异常)。
Exception
Exception类是所有检查型异常的父类。检查型异常是那些在编译时必须被处理(捕获或声明抛出)的异常。如果方法可能会抛出某个检查型异常,但没有捕获它,那么该方法必须通过throws关键字声明该异常。
检查型异常通常是外部错误,如尝试打开不存在的文件时发生的FileNotFoundException,或网络通信过程中发生的IOException。
RuntimeException
RuntimeException是Exception的子类,代表非检查型异常,即运行时异常。运行时异常包括了Java运行时环境可以自动抛出的异常。
与检查型异常不同,运行时异常是由程序逻辑错误引起的,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。编译器不要求强制处理或声明抛出运行时异常,因为它们通常是可以通过程序逻辑来预防的错误。
总结来说,Java的异常处理机制是通过Throwable类及其子类实现的。Error用于表示严重的系统级错误,通常是不可恢复的;Exception用于表示程序可以处理的异常,分为检查型异常和非检查型异常(RuntimeException)。正确理解和使用这些异常类对于编写健壮、可靠的Java程序至关重要。
以上将异常的特点介绍完毕,而在Springboot中开发时,需要考虑出现异常时的处理,已经抛出异常后如何将信息返回给前端,这就需要进行异常的统一处理
异常的统一处理
在Spring Boot项目中配置统一的异常处理是一种最佳实践,它不仅能够提升代码的可维护性和可读性,还能够为最终用户提供更加友好和一致的错误响应。通过实现统一异常处理,开发者可以集中处理应用中的各种异常,避免了在每个控制器或服务中重复编写错误处理逻辑,从而使得代码更加简洁和易于管理。下面我们将详细探讨未配置统一异常处理的坏处,以及配置后的好处
没有配置统一异常处理的坏处
- 代码重复:在没有统一异常处理的情况下,开发者可能需要在每个控制器或服务中编写异常处理代码,这会导致大量的重复代码,增加了代码的维护成本。
- 错误处理不一致:各个模块独立处理异常可能会导致错误响应的格式不一致,给前端开发和最终用户带来困扰,影响用户体验。
- 难以维护和扩展:随着项目规模的扩大,异常处理逻辑分散在各处,当需要修改错误响应格式或处理逻辑时,必须逐个修改,效率低下,容易遗漏。
- 调试困难:没有一个集中的异常处理机制,当出现错误时,定位和调试问题变得更加困难,特别是在复杂的应用中。
- 安全风险:不恰当的异常处理可能会泄露敏感信息给客户端,比如堆栈跟踪信息,这可能会导致安全漏洞。
配置统一异常处理的好处
- 减少代码重复:通过集中处理所有异常,可以显著减少代码重复,使得异常处理逻辑更加集中和一致。
- 提高代码可维护性:统一的异常处理机制使得代码更加整洁,易于管理和维护。对异常处理逻辑的修改和扩展可以在一个地方进行,提高了开发效率。
- 增强用户体验:统一异常处理允许开发者提供一致且友好的错误响应给用户,无论是API的消费者还是最终用户,都能从中受益。
- 便于错误监控和日志记录:集中处理异常使得监控和记录日志变得更加方便,有助于快速发现并解决问题。
- 提升应用的安全性:可以通过统一异常处理来过滤和处理敏感信息,防止敏感数据泄露给客户端,提升应用的安全性。
举例
为了能让小伙伴体会到异常统一配置的好处,我们来直接举例说明
执行成功情况
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"
}
到这里,小伙伴能发现区别了,发现没有处理异常的情况下,返回的数据结构发生了变化。这就是不允许的情况,因为前端分离的情况下,前端调用后端的数据返回的结构无论在成功或者失败的情况,数据结构都必须保持一致,否则前端根本没有办法解析。
付费内容提示
该文档的全部内容仅对「JavaUp项目实战&技术讲解」知识星球用户开放
加入星球后,你可以获得:
- 超级八股文:100万+字的全栈技术知识库,涵盖技术核心、数据库、中间件、分布式等深度剖析的讲解
- 讲解文档:黑马点评Plus、大麦、大麦pro、大麦AI、流量切换、数据中台的从0到1的550+详细文档
- 讲解视频:黑马点评Plus、大麦、大麦pro、大麦AI、流量切换、数据中台的核心业务详细讲解
- 1 对 1 解答:可以对我进行1对1的问题提问,而不仅仅只限于项目
- 针对性服务:有没理解的地方,文档或者视频还没有讲到可以提出,本人会补充
- 面试与简历指导:提供面试回答技巧,项目怎样写才能在简历中具有独特的亮点
- 中间件环境:对于项目中需要使用的中间件,可直接替换成我提供的云环境
- 面试后复盘:小伙伴去面试后,如果哪里被面试官问住了,可以再找我解答
- 远程的解决:如果在启动项目遇到问题,本人可以帮你远程解决
