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

news2025/4/21 11:52:17

一些好的异常处理实践。

目录

  • 异常设计
    • 自定义异常
    • 为异常设计错误代码(状态码)
    • 设计粒度
    • 全局异常处理
    • 异常日志信息保留
  • 异常处理时机
  • 资源管理
    • 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() { /* 获取锁或资源 */ }
    
    @Override
    public void close() { /* 释放锁或资源 */ }
}

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

异常中的事务

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

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

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

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

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

相关文章

海拔与大气压关系,大气压单位,气压传感器对比

mbmbar 毫巴(百帕) mbar 毫巴(百帕) hPa 百帕 1百帕1毫巴3/4毫米水银柱 1Kpa10百帕7.5毫米汞柱7.5mmhg 1Bar0.1MPa1000mba1000hpa100*7.5mmhg75mmhg1个大气压 HP303B HP303S HP203N BMP280

Linux 进程概念补充 (自用)

进程概念 内核进程进程状态内存泄漏进程调度。Linux真实调度算法环境变量 内核 狭义上的操作系统指的是 内核就是进程管理进程调度,文件系统等等。 广义上的操作系统其实在外壳指令这些。封装了系统调用的东西。 进程 课本概念程序的一个基本实例 内核观点&#…

PyTorch - Tensor 学习笔记

上层链接:PyTorch 学习笔记-CSDN博客 Tensor 初始化Tensor import torch import numpy as np# 1、直接从数据创建张量。数据类型是自动推断的 data [[1, 2],[3, 4]] x_data torch.tensor(data)torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])输出&am…

Navicat、DataGrip、DBeaver在渲染 BOOLEAN 类型字段时的一种特殊“视觉风格”

文章目录 前言✅ 为什么 Boolean 字段显示为 [ ]?✅ 如何验证实际数据类型?✅ 小结 前言 看到的 deleted: [ ] 并不是 Prisma 的问题,而是数据库客户端(如 Navicat、DataGrip、DBeaver)在渲染 BOOLEAN 类型字段时的一种…

基于 Vue3 + ECharts + GeoJson 实现区域地图钻取功能详解

文章目录 前言一、实现步骤1. 项目初始化2. 准备GeoJson数据3. 创建地图组件4. 创建主页面组件5. 使用组件 二、功能亮点三、性能优化建议四、常见问题解决五、结语六、实战demo七、资源下载 前言 在数据可视化领域,地图展示是一种非常直观的表现形式。而地图钻取&…

安卓学习24 -- 网络

1 整体架构 (出处见水印) 这两张是能找到的比较清楚的图。目前可以看出,底层的网络业务,还是传统的linux内核提供。(注:这两个图我个人觉得不是非常对。。。) 在安卓上增加的两个比较重要的部…

github新建一个远程仓库并添加了README.md,本地git仓库无法push

1.本地git仓库与远程仓库绑定 2.push时报错,本地的 main 分支落后于远程仓库的 main 分支(即远程有更新,但你本地没有),需要拉取远程的仓库--->在merge合并(解决冲突)--->push 3.但是git …

Python:使用web框架Flask搭建网站

Date: 2025.04.19 20:30:43 author: lijianzhan Flask 是一个轻量级的 Python Web 开发框架,以简洁灵活著称,适合快速构建中小型 Web 应用或 API 服务。以下是 Flask 的核心概念、使用方法和实践指南 Flask 的核心特点: 轻量级 核心代码仅约…

Kotlin delay方法解析

本文记录了kotlin协程(Android)中delay方法的字节码实现,并解析了delay方法如何实现挂起操作。 一、delay方法介绍 1.1、delay方法使用举例 class TestDelay {suspend fun testDelay() {Log.d("TestDelay", "before delay")delay(1000)Log.d…

【Vulkan 入门系列】创建描述符集布局和图形管线(五)

描述符集布局定义了着色器如何访问资源(如缓冲区和图像),是渲染管线配置的关键部分。图形管线定义了从顶点数据到最终像素输出的整个处理流程,包括可编程阶段(如顶点和片段着色器)和固定功能阶段&#xff0…

mysql中in的用法详解

MySQL 中 IN 操作符用法详解 IN 是 MySQL 中用于多值筛选的高效操作符,常用于 WHERE 子句,可替代多个 OR 条件,简化查询逻辑并提升可读性。以下从基础语法、应用场景、性能优化、常见问题及高级技巧进行全方位解析。 一、基础语法与优势 1.…

MySQL为什么默认使用RR隔离级别?

大家好,我是锋哥。今天分享关于【MySQL为什么默认使用RR隔离级别?】面试题。希望对大家有帮助; MySQL为什么默认使用RR隔离级别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL 默认使用 RR(Repeatable Read)…

施磊老师基于muduo网络库的集群聊天服务器(二)

文章目录 Cmake简单介绍Cmake与MakefileCmake配置CmakeLists.txt 编写完整cmake例子文件夹杂乱问题多级目录Cmakevscode 极其推荐 的 cmake方式 Mysql环境与编程mysql简单使用User表Friend表AllGroup表GroupUser表OfflineMessage表 集群聊天项目工程目录创建网络模块代码Chatse…

线性DP:最长上升子序列(子序列可不连续,子数组必须连续)

目录 Q1:简单遍历 Q2:变式(加大数据量) Q1:简单遍历 Dp问题 状态表示 f(i,j) 集合所有以第i个数结尾的上升子序列集合-f(i,j)的值存的是什么序列长度最大值max- 状态计算 (其实质是集合的划分)…

C语言之文本加密程序设计

🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 文本加密程序设计 摘要:本文设计了一种文本加密程序,旨在提高信息安…

云效部署实现Java项目自动化部署图解

前言 记录下使用云效部署Java项目,实现java项目一键化自动化部署。 云效流程说明: 1.云效拉取最新git代码后 2.进行maven编译打包后,上传到指定服务器目录 3.通过shell脚本,先kill java项目后,通过java -jar 启动项…

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…

基于Python智能体API的Word自动化排版系统:从零构建全流程模块化工作流与版本控制研究

基于Python智能体API的Word自动化排版系统:从零构建全流程模块化工作流与版本控制实践研究 1. 引言2. 研究背景与意义3. 自动排版工作流的设计原理3.1 文档内容提取与解析3.2 样式参数与格式化规则3.3 智能体API接口调用3.4 自动生成与批量处理3.5 与生成式AI的协同4. 系统架构…

Java【网络原理】(4)HTTP协议

目录 1.前言 2.正文 2.1自定义协议 2.2HTTP协议 2.2.1抓包工具 2.2.2请求响应格式 2.2.2.1URL 2.2.2.2urlencode 2.2.3认识方法 2.2.3.1GET与POST 2.2.3.2PUT与DELETE 2.2.4请求头关键属性 3.小结 1.前言 哈喽大家好啊,今天来继续给大家带来Java中网络…

每天学一个 Linux 命令(27):head

​​可访问网站查看,视觉品味拉满: http://www.616vip.cn/27/index.html head 是 Linux 中用于查看文件开头部分内容的命令,默认显示文件前 10 行,适合快速预览文件结构或日志头部信息。 命令格式 head [选项] [文件]常用选项 选项说明-n <行数>指定显示前 N 行(如…