Java开发经验——Throwable/Exception异常处理方式

摘要

文章主要探讨了 Java 开发中 Throwable 和 Exception 的异常处理方式。阿里巴巴 Java 开发手册规定,RPC 调用、二方包、动态代理类等场景推荐使用 Throwable,因为这些场景可能会出现类似 NoClassDefFoundError 这样的严重错误,使用 Throwable 可以防止遗漏。而在普通 Controller 代码中,推荐使用 Exception,因为使用 Throwable 可能会误吞 Error,而 Error 通常是 JVM 级别的严重问题,不应被业务代码处理。文章还总结了不同场景下使用 Throwable 和 Exception 的建议。

1. 规则主要是针对 RPC 调用、二方包(第三方 SDK)、动态代理类 这些场景,而不是普通的 Controller 代码

1.1. 为什么在 RPC、二方包、动态代理调用时需要使用 Throwable

  • 这些方法通常涉及外部依赖,可能会抛出 未受检的 Error(如 NoClassDefFoundErrorOutOfMemoryError)。
  • 业务代码需要确保,即使发生 Error,也能拦截到,避免影响整个应用的稳定性。

1.1.1. 示例: RPC 调用

try {RpcResponse response = remoteService.call(request);return response.getData();
} catch (Throwable t) {  // 这里使用 Throwable 兜底log.error("RPC 调用失败", t);return Response.error("远程服务调用失败");
}

如果远程服务抛出了 NoClassDefFoundErrorOutOfMemoryError,捕获 Throwable 可以保证不会影响整个应用。

1.1.2. 示例:调用第三方SDK

try {thirdPartyService.process();
} catch (Throwable t) {log.error("调用第三方服务异常", t);
}

如果第三方 SDK 发生 Error(如 ServiceConfigurationError),应用可以优雅地处理,而不会直接崩溃。

2. 在普通 Controller 代码中,应该使用 Exception

普通业务逻辑和 Controller 层代码,不应该捕获 Throwable,而是应该捕获 Exception,防止吞掉 Error
比如:

@PostMapping("/queryPage/list")
public Response<Page<OrderListDTO>> queryPageList(@RequestBody OrderQueryPageRequest request) {try {checkOrderQueryPageRequest(request);Page<OrderListDTO> queryPageResult = orderService.list(request);return Response.success(queryPageResult);} catch (IllegalArgumentException e) {log.warn("[进件管理] 参数错误: {}", e.getMessage(), e);return Response.error("[进件管理] 参数错误:" + e.getMessage());} catch (Exception e) {log.error("[进件管理] 分页查询异常", e);return Response.error("[进件管理] 分页查询异常:" + e.getMessage());}
}

Controller 层,Exception 足够处理常见的业务异常,没有必要使用 Throwable,因为:

  • Throwable 会捕获 Error,但 Error 通常表示 JVM 级别的严重问题,不应该被业务代码处理。
  • Exception 已经足够处理大部分的 业务异常运行时异常RuntimeException)。

3. Throwable/Exception异常处理方式总结

3.1. 什么时候用 Throwable

场景

使用 Throwable

使用 Exception

RPC 调用(远程服务)

推荐,防止 NoClassDefFoundError

❌ 可能遗漏 Error

二方包(第三方 SDK)

推荐,防止 ServiceConfigurationError

❌ 可能遗漏 Error

动态代理调用(如 CGLib、JDK Proxy)

推荐,防止 AbstractMethodError

❌ 可能遗漏 Error

普通业务代码(如 Service、Controller)

不推荐,会误吞 Error

推荐,保证异常处理可控

Spring 全局异常处理(@ControllerAdvice

可以,兜底处理所有异常

推荐

3.2. 异常处理的最终结论

在 Controller 代码中,应该使用 Exception

  • ❌ 不要 catch (Throwable t)
  • ✅ 推荐 catch (Exception e)

在调用 RPC、第三方 SDK、动态代理时,使用 Throwable

  • catch (Throwable t) { log.error("异常", t); }
  • 这样可以防止 Error 直接导致应用崩溃

在全局异常处理(@ControllerAdvice)中,可以使用 Throwable

  • 防止 Error 影响整个应用,但 Controller 层本身仍然应该捕获 Exception

4. 通用Spring全局异常处理类

4.1. 全局异常处理类

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** 全局异常处理器*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 处理业务异常(如参数错误、校验失败等)*/@ExceptionHandler(IllegalArgumentException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Response<String> handleIllegalArgumentException(IllegalArgumentException e) {log.warn("[参数异常] {}", e.getMessage(), e);return Response.error("参数错误:" + e.getMessage());}/*** 处理通用业务异常*/@ExceptionHandler(BusinessException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Response<String> handleBusinessException(BusinessException e) {log.warn("[业务异常] {}", e.getMessage(), e);return Response.error("业务异常:" + e.getMessage());}/*** 处理所有运行时异常*/@ExceptionHandler(RuntimeException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Response<String> handleRuntimeException(RuntimeException e) {log.error("[系统异常] ", e);return Response.error("系统错误,请联系管理员");}/*** 兜底异常处理(Throwable),防止 Error 影响系统稳定性*/@ExceptionHandler(Throwable.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Response<String> handleThrowable(Throwable t) {log.error("[严重错误] ", t);return Response.error("系统发生严重错误,请稍后重试");}
}

4.1.1. 关键点说明

  1. @RestControllerAdvice
    • 作用于所有 @RestController,统一拦截异常并返回 JSON 响应。
    • 如果是 @ControllerAdvice,需要配合 @ResponseBody 才能返回 JSON。
  1. 异常分类处理
    • IllegalArgumentException:参数错误,如 Assert 失败、入参校验不通过。
    • BusinessException:自定义业务异常,代表业务逻辑失败(如订单状态异常)。
    • RuntimeException:所有运行时异常(NullPointerExceptionIndexOutOfBoundsException)。
    • Throwable(兜底):
      • 避免 Error(如 OutOfMemoryError)直接导致应用崩溃。
      • 但一般不应该在业务代码里捕获 Throwable
  1. 日志级别
    • warn:业务异常,开发人员关注即可。
    • error:系统异常或 Throwable,需要运维排查。

4.2. 自定义业务异常类

public class BusinessException extends RuntimeException {public BusinessException(String message) {super(message);}public BusinessException(String message, Throwable cause) {super(message, cause);}
}

4.3. 全局异常处理示例

@RestController
@RequestMapping("/order")
public class OrderController {@GetMapping("/{id}")public Response<OrderDTO> getOrder(@PathVariable Long id) {if (id == null || id <= 0) {throw new IllegalArgumentException("订单 ID 不能为空或小于等于 0");}if (id == 999) {throw new BusinessException("订单不存在");}return Response.success(new OrderDTO(id, "测试订单"));}
}

4.3.1. 返回结果示例

4.3.1.1. 业务异常(参数错误)

请求

GET /order/-1

返回

{"code": "ERROR","message": "参数错误:订单 ID 不能为空或小于等于 0"
}
4.3.1.2. 业务异常(订单不存在)

请求

GET /order/999

返回

{"code": "ERROR","message": "业务异常:订单不存在"
}
4.3.1.3. 系统异常

如果代码出现 NullPointerException

{"code": "ERROR","message": "系统错误,请联系管理员"
}

4.4. 如果没有全局统一处理。Controller 层要不要直接 catch 异常?

没有全局异常处理(@ControllerAdvice 的情况下,Controller 层需要自己 catch 异常,但要遵循以下 最佳实践

4.5. 🚫 不推荐:直接不捕获

如果不 catch 异常,Spring MVC 默认会返回 500 Internal Server Error,但:

  • 前端无法区分是 业务异常 还是 系统异常
  • 日志可能没有详细的错误信息,不利于排查问题。
  • 可能会暴露敏感信息(如 NullPointerException 可能会泄露内部字段结构)。

4.6. 推荐方案:在 Controller 层 catch 业务异常,但不处理系统异常

4.6.1. 示例:在 Controller 里手动捕获业务异常

@PostMapping("/queryPage/list")
public Response<Page<OrderListDTO>> queryPageList(@RequestBody OrderQueryPageRequest request) {try {checkOrderQueryPageRequest(request);Page<OrderListDTO> queryPageResult = orderService.list(request);return Response.success(queryPageResult);} catch (IllegalArgumentException e) {// 业务异常(如参数校验失败)log.warn("[进件管理] 参数错误: {}", e.getMessage(), e);return Response.error("[进件管理] 参数错误:" + e.getMessage());} catch (BusinessException e) {// 自定义业务异常log.warn("[进件管理] 业务异常: {}", e.getMessage(), e);return Response.error("[进件管理] 业务异常:" + e.getMessage());} catch (Exception e) {// **系统异常** 直接抛出,不吞掉,避免影响排查log.error("[进件管理] 系统异常", e);throw e;  // 让 Spring 处理,避免误吞}
}

4.7. 🔥 关键点说明

  1. 业务异常(参数错误、业务失败)自己处理
    • IllegalArgumentException(参数错误)
    • BusinessException(自定义业务异常)
    • 返回友好的错误信息,不让前端看到堆栈信息
  1. 系统异常(Exception)不直接处理
    • NullPointerException
    • IndexOutOfBoundsException
    • DatabaseException
    • 直接抛出,避免误吞 Error,并确保日志完整
  1. 日志级别
    • 业务异常 warn开发关注
    • 系统异常 error运维关注

4.8. 反面示例:全部 catch 但不抛出

catch (Exception e) {log.error("[进件管理] 查询异常:" + e.getMessage(), e);return Response.error("[进件管理] 查询失败");
}
  • 问题
    1. 吞掉异常,后续代码不知道哪里出了问题。
    2. 所有异常都变成普通业务异常,影响监控和排查。
    3. Error 也被吞掉,可能导致 JVM 崩溃时没有日志。

4.9. 🚀 最佳方案

  1. 业务异常Controller 层 catch 并返回友好信息
  2. 系统异常 让 Spring 兜底,避免误吞(可以配合 @ControllerAdvice)。
  3. 错误日志要区分
    • 业务异常warn(轻量级,不影响系统)
    • 系统异常error(需要关注和报警)

4.10. 💡 结论

4.10.1. 如果没有全局异常处理

  • Controller 层 需要 catch 业务异常,防止影响用户体验。
  • 系统异常 不要吞掉,应抛出给 Spring 处理。

4.10.2. 🚀 最佳做法

  • 业务异常(如 IllegalArgumentExceptionBusinessException)自己 catch 并返回友好信息。
  • 其他异常(如 NullPointerException、数据库异常)直接抛出,避免误吞。
  • 最终还是建议使用 @ControllerAdvice 统一管理,让代码更简洁!

代码位置

处理的异常

使用的异常类型

返回状态码

Controller

参数错误

IllegalArgumentException

400 BAD_REQUEST

Service

业务逻辑异常

BusinessException

400 BAD_REQUEST

全局异常处理

运行时异常

RuntimeException

500 INTERNAL_SERVER_ERROR

全局异常处理

未知错误

Throwable

500 INTERNAL_SERVER_ERROR

4.10.3. 最佳实践

  1. 业务代码中 只捕获 Exception,避免误吞 Error
  2. 调用 RPC/第三方 SDK 时,建议捕获 Throwable 兜底,防止 Error 影响系统稳定。
  3. Controller 层不要直接 catch 异常,让全局异常处理器统一管理。
  4. 不同类型的异常,返回不同的 HTTP 状态码,方便前端或调用方识别。

博文参考

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

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

相关文章

[Mysql]创建数据库基础

数据库意义 更加利于管理的东西-数据库&#xff0c;他能有效的管理数据 举例一个生活化的案例说明 如果说&#xff0c;图书馆是保存书籍的&#xff0c;那么数据库技术保存数据的 数据库的简单原理图 Mysql数据库三层结构与本质 数据库管理系统与 mysqld&#xff1a;MySQL 数…

AMBA-CHI协议详解(二十五)

AMBA-CHI协议详解&#xff08;一&#xff09;- Introduction AMBA-CHI协议详解&#xff08;二&#xff09;- Channel fields / Read transactions AMBA-CHI协议详解&#xff08;三&#xff09;- Write transactions AMBA-CHI协议详解&#xff08;四&#xff09;- Other transac…

【RabbitMQ】RabbitMQ的基本架构是什么?包括哪些核心组件?

RabbitMQ基于AMQP协议实现&#xff0c;由多个核心组件组成&#xff0c;确保消息的可靠传递。 Rabbit的架构图&#xff1a; 1.RabbitMQ的基本架构&#xff1a; 1.核心组件&#xff1a; 1.Producer(生产者)&#xff1a; 发送消息到RabbitMQ。 2.Exchange(交换机)&#xff1a;接…

【PCB工艺】基础:电子元器件

电子原理图&#xff08;Schematic Diagram&#xff09;是电路设计的基础&#xff0c;理解电子元器件和集成电路&#xff08;IC&#xff09;的作用&#xff0c;是画好原理图的关键。 本专栏将系统讲解 电子元器件分类、常见 IC、电路设计技巧&#xff0c;帮助你快速掌握电子电路…

Html label标签中的for属性(关联表单控件:将标签与特定的表单元素(如输入框、复选框等)关联起来;提高可用性;无障碍性)

文章目录 示例代码for属性含义完整代码示例 示例代码 <div class"form-group"> <!-- 表单组&#xff0c;包含省份输入框和标签 --><label for"province">省份名称&#xff1a;</label> <!-- 省份输入框的标签 --><input…

S32K144外设实验(二):ADC单通道单次采样(软件触发)

文章目录 1. 概述1.1 理论回顾1.1.1 时钟系统1.1.2 采样通道1.2 实验目的2. 配置与代码编写1. 概述 1.1 理论回顾 S32K144的ADC应该说是特别灵活,笔者采用循序渐进的方式来学习使用这个很重要的外设。 在《入门笔记系列》专栏中对用户手册进行了翻译和解读,这里在回顾一下A…

进程控制~

一.进程控制 1.进程创建 我们可以通过./cmd来运行我们的程序&#xff0c;而我们运行的程序就是bash进程常见的子进程。当然我们也可以通过fork()系统调用来创建进程。 NAME fork - create a child process SYNOPSIS #include <unistd.h> pid_t fork(void…

经历过的IDEA+Maven+JDK一些困惑

注意事项&#xff1a;由于使用过程中是IDEA绑定好另外2个工具&#xff0c;所以报错统一都显示在控制台&#xff0c;但要思考和分辨到底是IDEA本身问题导致的报错&#xff0c;还是maven导致的 标准配置 maven Java Compiler Structure 编辑期 定义&#xff1a;指的是从open pr…

将bin文件烧录到STM32

将bin文件烧录到STM32 CoFlash下载生成hex文件hex2bin使用下载bin到单片机 CoFlash下载 选择需要安装的目录 在Config中可以选择目标芯片的类型 我演示的是 stm32f103c8t6 最小系统板 Adapter&#xff1a;烧录器类型 Max Clock&#xff1a;下载速度 Por&#xff1a;接口类型&am…

硬件基础(5):(2)二极管分类

文章目录 &#x1f4cc; 二极管的分类与详细介绍1. **整流二极管&#xff08;Rectifier Diode&#xff09;**特点&#xff1a;选型依据&#xff1a;补充说明&#xff1a; 2. **快恢复二极管&#xff08;Fast Recovery Diode&#xff09;**特点&#xff1a;选型依据&#xff1a;…

【MySQL】MySQL如何存储元数据?

目录 1.数据字典的作用 2. MySQL 8.0 之前的数据字典 3. MySQL 8.0 及之后的数据字典 4.MySQL 8 中的事务数据字典的特征 5.数据字典的序列化 6. .sdi文件的作用&#xff1a; 7..sdi的存储方式 在 MySQL 中&#xff0c;元数据&#xff08;Metadata&#xff09; 是描述数…

瑞萨RA系列使用JLink RTT Viewer输出调试信息

引言 还在用UART调试程序么?试试JLINK的RTT Viewer吧!不需占用UART端口、低资源暂用、实时性高延时微秒级,这么好的工具还有什么理由不用了! 目录 一、JLink RTT Viewer 简介 二、软件安装 三、工程应用 3.1 SEGGER_RTT驱动包 3.2 手搓宏定义APP_PRINT 3.3 使用APP_…

Ranger 鉴权

Apache Ranger 是一个用来在 Hadoop 平台上进行监控&#xff0c;启用服务&#xff0c;以及全方位数据安全访问管理的安全框架。 使用 ranger 后&#xff0c;会通过在 Ranger 侧配置权限代替在 Doris 中执行 Grant 语句授权。 Ranger 的安装和配置见下文&#xff1a;安装和配置 …

LabVIEW烟气速度场实时监测

本项目针对燃煤电站烟气流速实时监测需求&#xff0c;探讨了静电传感器结构与速度场超分辨率重建方法&#xff0c;结合LabVIEW多板卡同步采集与实时处理技术&#xff0c;开发出一个高效的烟气速度场实时监测系统。该系统能够在高温、高尘的复杂工况下稳定运行&#xff0c;提供高…

【系统架构设计师】操作系统 - 特殊操作系统 ③ ( 微内核操作系统 | 单体内核 操作系统 | 内核态 | 用户态 | 单体内核 与 微内核 对比 )

文章目录 一、微内核操作系统1、单体内核 操作系统2、微内核操作系统 引入3、微内核操作系统 概念4、微内核操作系统 案例 二、单体内核 与 微内核 对比1、功能对比2、单体内核 优缺点3、微内核 优缺点 一、微内核操作系统 1、单体内核 操作系统 单体内核 操作系统 工作状态 : …

人工智能之数学基础:线性方程组

本文重点 线性方程组是由两个或两个以上的线性方程组成的方程组,其中每个方程都是关于两个或两个以上未知数的线性方程。 记忆恢复 我们先从小学学习的线性方程组找到感觉 解答过程: 将第二个方程乘以2,得到: 2x−2y=2 将第一个方程减去新得到的方程,消去x: (2x+y)−…

​第十一届传感云和边缘计算系统国际会议

重要信息 时间地点&#xff1a;2025年4月18-20日 中国-珠海 会议官网&#xff1a;www.scecs.org 简介 第十一届传感云和边缘计算系统 (SCECS 2025&#xff09;将于2025年4月18-20日在中国珠海召开。将围绕“传感云”、“边缘计算系统”的最新研究领域&#xff0c;为来自国…

MDM设备管控,企业移动设备管理方案

目录&#xff1a; 目录 目录&#xff1a; 1. MDM&#xff1a;含义与定义 2. MDM如何工作&#xff1f; 3. BYOD与MDM&#xff1a;挑战与解决方案 4. 移动设备管理的主要优势 5. 移动设备管理的基本要素 6. 移动设备管理最佳实践 --地平线-- 移动设备管理 (MDM)历经多年…

S32k3XX MCU时钟配置

今天想从头开始配置S32K312中EB中的MCU模块&#xff0c;以下是我的配置思路与理解。 关键是研究明白&#xff0c;这些频率是如何通过一个总时钟&#xff0c;一步步分频得到的。 参考时钟&#xff0c;供外设模块使用&#xff0c;不同外设需要配置合理的参考时钟。 clock genera…

GitHub 超火的开源终端工具——Warp

Warp 作为近年来 GitHub 上备受瞩目的开源终端工具&#xff0c;以其智能化、高性能和协作能力重新定义了命令行操作体验。以下从多个维度深入解析其核心特性、技术架构、用户评价及生态影响力&#xff1a; 一、背景与核心团队 Warp 由前 GitHub CTO Jason Warner 和 Google 前…