[Java实战经验]异常处理最佳实践

一些好的异常处理实践。

目录

  • 异常设计
    • 自定义异常
    • 为异常设计错误代码(状态码)
    • 设计粒度
    • 全局异常处理
    • 异常日志信息保留
  • 异常处理时机
  • 资源管理
    • try-with-resources
    • 异常中的事务

异常设计

自定义异常

自定义异常设计,如业务异常定义BusinessException,设置一个基础异常类,如XXAppBaseException(或就叫BaseException),然后让各类异常继承,如下:

public class UserException extends XXAppBaseException { ... }public class MapException extends XXAppBaseException { ... }

这里异常的划分可以按照模块、业务来区分,也可以分离业务代码异常与技术代码异常。

为异常设计错误代码(状态码)

常见的异常代码设计有HTTP的异常状态码,如404、500、502这种。
这样做主要是便于日志分析和客户端处理,很明显,使用错误代码做筛选能提升检索效率、方便收集、自动化处理,且使用异常状态码来传输异常信息提升了信息传输与存储效率。
等等……

设计粒度

自定义异常和异常错误代码都是比较常见的操作,但是设计时需要考虑粒度。

一般有层级关系的设计更便于理解、维护。

在自定义异常中就是多层继承关系,在异常错误码中就是分层错误码设计,如全局错误码 > 模块错误码 > 具体错误码

5xx—>5xxx->5xxxx

全局异常处理

使用Spring的@ControllerAdvice或类似机制统一处理异常。如:

/*** 全局异常处理器* * @author ruoyi*/
@RestControllerAdvice
public class GlobalExceptionHandler
{private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 权限校验异常(ajax请求返回json,redirect请求跳转页面)*/@ExceptionHandler(AuthorizationException.class)public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request){String requestURI = request.getRequestURI();log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());if (ServletUtils.isAjaxRequest(request)){return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));}else{return new ModelAndView("error/unauth");}}/*** 请求方式不支持*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,HttpServletRequest request){String requestURI = request.getRequestURI();log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());return AjaxResult.error(e.getMessage());}/*** 拦截未知的运行时异常*/@ExceptionHandler(RuntimeException.class)public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request){String requestURI = request.getRequestURI();log.error("请求地址'{}',发生未知异常.", requestURI, e);return AjaxResult.error(e.getMessage());}/*** 系统异常*/@ExceptionHandler(Exception.class)public AjaxResult handleException(Exception e, HttpServletRequest request){String requestURI = request.getRequestURI();log.error("请求地址'{}',发生系统异常.", requestURI, e);return AjaxResult.error(e.getMessage());}/*** 业务异常*/@ExceptionHandler(ServiceException.class)public Object handleServiceException(ServiceException e, HttpServletRequest request){log.error(e.getMessage(), e);if (ServletUtils.isAjaxRequest(request)){return AjaxResult.error(e.getMessage());}else{return new ModelAndView("error/service", "errorMessage", e.getMessage());}}
}

异常日志信息保留

在抛出异常时,建议连带当前业务标识信息一起抛出(每一层日志抛出记录标识),这样方便排查问题。

异常处理时机

偶尔能看到一类将所有代码块都包起来的try-catch异常处理,这种代码被称为“防御式编程”的过度使用,会导致多种问题。

  1. 代码可读性降低:大量的异常处理代码掩盖了业务逻辑
  2. 异常信息丢失:每层都捕获并重新抛出,可能丢失原始堆栈信息
  3. 难以维护:当需要修改异常处理逻辑时,需要修改多处代码
  4. 性能影响:过多的try-catch会对性能产生轻微影响

异常处理应该在能够正确响应的层级进行,如:

  1. 边界层处理:如API层、用户界面层、外部系统集成点
  2. 业务决策点处理:在能够做出恢复决策的地方处理异常
  3. 资源管理点处理:在使用需要清理的资源的地方处理
// 不好的做法
public void badExceptionHandling() {try {// 获取用户User user = null;try {user = userRepository.findById(userId);} catch (Exception e) {log.error("Failed to get user", e);}// 获取订单Order order = null;try {order = orderRepository.findById(orderId);} catch (Exception e) {log.error("Failed to get order", e);}// 处理业务逻辑try {processOrder(user, order);} catch (Exception e) {log.error("Failed to process order", e);}} catch (Exception e) {log.error("Unexpected error", e);}
}
// 好的做法
public void goodExceptionHandling() {try {User user = userRepository.findById(userId);Order order = orderRepository.findById(orderId);processOrder(user, order);} catch (UserNotFoundException e) {// 有针对性地处理用户不存在的情况log.warn("Order processing failed: User not found", e);notifyAdministrator(e);} catch (OrderNotFoundException e) {// 有针对性地处理订单不存在的情况log.warn("Order processing failed: Order not found", e);notifyCustomer(userId, e);} catch (BusinessException e) {// 处理所有业务异常log.warn("Business rule violation during order processing", e);// 可能的补救措施} catch (Exception e) {// 处理所有其他未预期的异常log.error("Unexpected error during order processing", e);// 紧急措施}
}

资源管理

try-with-resources

使用try-with-resources自动关闭资源,防止泄露。为自己的资源类实现AutoCloseable接口,如:

public class ResourceLock implements AutoCloseable {// 获取资源public ResourceLock() { /* 获取锁或资源 */ }@Overridepublic void close() { /* 释放锁或资源 */ }
}

使用finally和这个一样。常规操作。

异常中的事务

在Spring框架中,默认情况运行时异常与严重问题会导致事务回滚,检查型异常不会。

  • 运行时异常(unchecked):继承自RuntimeException的异常,默认导致事务回滚
  • 检查型异常(checked):继承自Exception但不是RuntimeException的子类,默认不会导致事务回滚
  • Error:严重问题,如OutOfMemoryError,默认导致事务回滚

因此,我们需要注意异常对事务的影响。

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

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

相关文章

Makefile 入门指南

Makefile 入门指南 最简单的例子 单文件编译 假设我们有一个main.cpp文件,最简单的Makefile如下: # 最简单的单文件编译 # 目标:依赖文件 main: main.cpp# 编译命令g main.cpp -o main使用步骤: 将上述内容保存为名为Makefile的文件&…

PyTorch数据操作基础教程:从张量创建到高级运算

本文通过示例代码全面讲解PyTorch中张量的基本操作,包含创建、运算、广播机制、索引切片等核心功能,并提供完整的代码和输出结果。 1. 张量创建与基本属性 import torch# 创建连续数值张量 x torch.arange(12, dtypetorch.float32) print("原始张…

【Redis】Redis中的常见数据类型(一)

文章目录 前言一、Redis前置知识1. 全局命令2、数据结构和内部编码3. 单线程架构 二、String 字符串1. 常见命令2. 计数命令3.其他命令4. 内部编码5. 典型使用场景 三、Hash哈希1. 命令2.内部编码3. 使用场景4. 缓存方式对比 结语 前言 Redis 提供了 5 种数据结构,…

Windows 中使用 `netstat` 命令查看端口占用

在 Windows 系统中,可以通过 netstat 命令来查看当前系统的网络连接以及端口的占用情况。以下是关于该命令的具体说明: #### 使用方法 1. **查看所有端口及其状态** 可以通过以下命令查看系统中的所有活动连接和监听端口: bash net…

23种设计模式-结构型模式之装饰器模式(Java版本)

Java 装饰器模式(Decorator Pattern)详解 🎁 什么是装饰器模式? 装饰器模式是一种结构型设计模式,允许向一个对象动态添加新的功能,而不改变其结构。 🧱 你可以想象成在原有功能上“包裹”一…

解决模拟器打开小红书设备异常问题

解决模拟器打开小红书设备异常问题 解决模拟器打开小红书设备异常问题和无法打开问题 解决模拟器打开小红书设备异常问题和无法打开问题 问题描述 最近有用户反馈在模拟器上无法正常登录和打开小红书APP,系统提示"设备异常"错误。本文将详细介绍如何通过…

论文阅读:2025 arxiv AI Alignment: A Comprehensive Survey

总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 AI Alignment: A Comprehensive Survey 人工智能对齐:全面调查 https://arxiv.org/pdf/2310.19852 https://alignmentsurvey.com/ https://www.doubao.com/cha…

精益数据分析(1/126):从《精益数据分析》探寻数据驱动增长之道

精益数据分析(1/126):从《精益数据分析》探寻数据驱动增长之道 在当今数字化时代,数据无疑是企业发展的关键驱动力,对于竞争激烈的程序化广告行业更是如此。最近我在研读《精益数据分析》这本书,收获颇丰&…

第五节:React Hooks进阶篇-如何用useMemo/useCallback优化性能

反模式:滥用导致的内存开销React 19编译器自动Memoization原理 React Hooks 性能优化进阶:从手动到自动 Memoization (基于 React 18 及以下版本,结合 React 19 新特性分析) 一、useMemo/useCallback 的正确使用场景…

windows server C# IIS部署

1、添加IIS功能 windows server 2012、windows server 2016、windows server 2019 说明:自带的是.net 4.5 不需要安装.net 3.5 尽量使用 windows server 2019、2016高版本,低版本会出现需要打补丁的问题 2、打开IIS 3、打开iis应用池 .net 4.5 4、添…

Elasticsearch的Java客户端库QueryBuilders查询方法大全

matchAllQuery 使用方法:创建一个查询,匹配所有文档。 示例:QueryBuilders.matchAllQuery() 注意事项:这种查询不加任何条件,会返回索引中的所有文档,可能会影响性能,特别是文档数量很多时。 ma…

C#进阶学习(六)单向链表和双向链表,循环链表(下)循环链表

目录 📊 链表三剑客:特性全景对比表 一、循环链表节点类 二、循环链表的整体设计框架 三、循环列表中的重要方法: (1)头插法,在头结点前面插入新的节点 (2)尾插法实现插入元素…

交换网络基础

学习目标 掌握交换机的基本工作原理 掌握交换机的基本配置 交换机的基本工作原理 交换机是局域网(LAN)中实现数据高效转发的核心设备,工作在 数据链路层(OSI 模型第二层),其基本工作原理可概括为 “学习…

科学研究:怎么做

科研(科学研究)​​ 是指通过系统化的方法,探索自然、社会或人文领域的未知问题,以发现新知识、验证理论或解决实际问题的活动。它的核心是​​基于证据的探索与创新​​,旨在推动人类认知和技术的进步。 科研的核心要…

算法题(128):费解的开关

审题: 本题需要我们将多组测试用例中拉灯数小于等于6的最小拉灯数输出,若拉灯数最小值仍大于6,则输出-1 思路: 方法一:二进制枚举 首先我们先分析一下基本特性: 1.所有的灯不可能重复拉:若拉的数…

MFC文件-屏幕录像

下载本文件 本文件将获取屏幕图像数据的所有代码整合到两个文件中(ScreenRecorder.h和ScreenRecorder.cpp),使获取屏幕图像数据变得简单。输出IYUV视频流。还可以获取系统播放的声音,输出PCM音频流。由于使用了MFC类,本…

0801ajax_mock-网络ajax请求1-react-仿低代码平台项目

0 vite配置proxy代理 vite.config.ts代码如下图所示: import { defineConfig } from "vite"; import react from "vitejs/plugin-react";// https://vite.dev/config/ export default defineConfig({plugins: [react()],server: {proxy: {&qu…

JVM笔记【一】java和Tomcat类加载机制

JVM笔记一java和Tomcat类加载机制 java和Tomcat类加载机制 Java类加载 * loadClass加载步骤类加载机制类加载器初始化过程双亲委派机制全盘负责委托机制类关系图自定义类加载器打破双亲委派机制 Tomcat类加载器 * 为了解决以上问题,tomcat是如何实现类加载机制的…

IP编址(来自YESLAB新网工的笔记)

上层协议类型 概念:通常指的是位于网络层(如 IP 层)以上的协议类型,这些协议在数据传输时需要由网络层(或更低层)协议承载。以 IP 协议为例,IP 报文头部中的 协议字段(Protocol Fie…

SpringBoot学习(过滤器Filter。拦截器Interceptor。全局异常捕获处理器GlobalExceptionHandler)(详细使用教程)

目录 一、过滤器Filter。 1.1定义与规范。 1.2工作原理与范围。 1.3使用场景。 1.4 SpringBoot实现过滤器。&#xff08;Filter配置2种方式&#xff09; <1>注解配置(WebFilter、Order、ServletComponentScan)。 创建过滤器类。 启用 Servlet 组件扫描。 <2>配置类…