Spring | 异常处理最佳实践

news2024/12/28 6:41:46

引言

在快速迭代和持续交付的今天,软件的健壮性、可靠性和用户体验已经成为区别成功与否的关键因素。特别是在Spring框架中,由于其广泛的应用和丰富的功能,如何优雅地处理异常就显得尤为重要。本文旨在探讨在Spring中如何更加高效、准确和优雅地处理异常,帮助开发者更好地构建和维护Spring应用。

目的与背景

通过本文,读者将深入了解Spring框架中的异常处理机制和策略,学习如何利用Spring提供的工具和注解来实现优雅的异常处理,从而提高软件的可用性和用户满意度。

文章结构概述

本文首先会简要介绍异常处理的基础知识和其在软件开发中的重要性。接着,我们会深入探讨Spring内置的异常处理机制,包括@ExceptionHandler@ControllerAdviceResponseEntityExceptionHandlerErrorController等,并通过实战演示和代码示例来展示如何在实际项目中运用这些机制。
在此基础上,我们还会探讨如何自定义异常处理策略,设计统一的异常响应格式,以及创建和管理业务相关的异常类。此外,文章还会详细讨论状态码与异常的关联,异常日志记录的最佳实践,全局与局部的异常处理策略,以及异常处理的测试策略。

以下所有示例均已上传至Github上,大家可以将项目拉取到本地进行运行
Github示例(如果对Gradle还不熟练,建议翻看我之前的文章):gradle-spring-boot-demo


异常处理的基础知识

在探索Spring中的异常处理机制和策略前,理解异常处理的基础知识是至关重要的。异常是程序运行时发生的不正常情况,可能导致程序的预期行为偏离或终止。在Java中,异常用Exception类或其子类表示,并需被捕获并处理。妥善处理异常能提升程序的健壮性稳定性,优化用户体验,并防止可能的数据丢失或系统崩溃。

1.1 异常的分类

Java中的异常主要分为受检异常非受检异常

  • 受检异常: 受检异常是编译器要求必须处理的异常,常由外部因素如文件未找到、网络连接失败等引起。开发者必须在代码中显式地捕获并处理这类异常,或通过throws关键字声明抛出。
  • 非受检异常:非受检异常,或称运行时异常,常由程序逻辑错误如空指针、数组越界等引起。Java编译器不会强制开发者处理非受检异常,但在实际开发中,合理地捕获和处理这类异常是非常重要的。

小结

深入理解异常的概念分类是学习Spring异常处理机制的基础。清晰了解异常的性质和种类可以使我们更准确、高效地处理异常,充分发挥Spring框架提供的异常处理能力。


Spring内置的异常处理机制

Spring框架为我们提供了一套丰富而完善的异常处理机制,这套机制允许我们在发生异常时能够做出快速且正确的响应,确保程序的稳定性和用户体验。本章我们将探讨Spring中的主要异常处理机制。

2.1 @ExceptionHandler

@ExceptionHandler注解用于在控制器(Controller)内处理异常。这个注解通常与特定的异常类一起使用,用于处理控制器中可能抛出的该异常。通过@ExceptionHandler,我们可以将异常映射到特定的处理方法,返回定制的错误响应。

2.1.1 使用示例
@RestController
public class MyController {

    @GetMapping("/endpoint")
    public String endPoint() throws Exception {
        throw new Exception("异常出错!");
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleMyException(Exception e) {
        return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

话不多说,我们启动项目,访问http://127.0.0.1:8080/endpoint 页面
在这里插入图片描述
我在这里抛出了异常,紧接着异常就被捕获到了:
在这里插入图片描述

2.2 @ControllerAdvice

@ControllerAdvice是一个全局异常处理注解,它可以捕获所有控制器中抛出的异常。与@ExceptionHandler结合使用,可以实现全局的异常处理策略,保持错误响应的一致性。

2.2.1 使用示例

我们把上面异常捕获注释掉,并添加如下代码:

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MyException.class)
    public ResponseEntity<String> handleMyException(MyException e) {
        return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

同样的访问http://127.0.0.1:8080/endpoint 页面,异常被该类捕获到了
在这里插入图片描述
页面输出如下内容:
在这里插入图片描述

2.3 ResponseEntityExceptionHandler

ResponseEntityExceptionHandler是一个基础类,我们可以通过继承这个类并覆盖其中的方法,来处理由Spring内部抛出的一系列标准异常,例如MethodArgumentNotValidException等。

2.3.1 使用示例
@RestControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex,
            HttpHeaders headers,
            HttpStatusCode status,
            WebRequest request){

        // 获取所有的错误信息
        List<String> errorDetails = ex.getBindingResult()
                                      .getFieldErrors()
                                      .stream()
                                      .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
                                      .collect(Collectors.toList());

        // 创建一个错误响应体对象
        ApiError apiError = new ApiError(
            LocalDateTime.now(),
            HttpStatus.BAD_REQUEST,
            "Validation Failed",
            errorDetails
        );

        // 返回定制的错误响应体
        return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST);
    }
}

MyController添加如下方法:

    @PostMapping("/user")
    public ResponseEntity<String> createUser(@Valid @RequestBody User user) {
        return new ResponseEntity<>("User created successfully!" + user.toString(), HttpStatus.CREATED);
    }

写一个测试用例:

    /**
     * 参数校验
     * {@link MyController#createUser}
     */
    @Test
    void shouldReturnBadRequestWhenNameIsBlank() throws Exception {
        String userJson = "{\"name\":\"\"}"; // 空的name应该触发验证失败

        mockMvc.perform(post("/user")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(userJson))
                .andExpect(status().isBadRequest());
    }

执行!参数校验异常被成功捕获到
在这里插入图片描述
咳咳,这样控制台没办法打印,我们使用postman来看下,执行结果如下:
在这里插入图片描述
参数校验异常被捕获到了,非常清晰:
在这里插入图片描述

2.4 ErrorController

通过实现ErrorController接口,我们可以定制错误映射和错误页面,为用户提供更友好的错误提示。

2.4.1 使用示例
@RestController
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public ResponseEntity<Map<String, Object>> handleError() {
        // Customize the error response
        return new ResponseEntity<>(...);
    }
    
    @Override
    public String getErrorPath() {
        return "/error";
    }
}

小结

Spring提供的内置异常处理机制,如@ExceptionHandler@ControllerAdviceResponseEntityExceptionHandlerErrorController,允许我们针对不同场景和需求,实现灵活且全面的异常处理策略。通过熟练运用这些工具,我们可以构建出更加稳定、健壮且用户友好的应用。


自定义异常处理

虽然Spring提供了一套丰富的异常处理机制,但在某些情况下,我们可能会需要更加个性化和灵活的异常处理策略。在这种情况下,我们可以通过自定义异常处理来满足我们的需求。以下,我们将探讨如何在Spring中实现自定义异常处理。

3.1 定义自定义异常

自定义异常通常继承自RuntimeExceptionException。通过创建自定义异常,我们可以更精确地表达和捕获特定的错误情况。

3.1.1 创建自定义异常
public class CustomException extends RuntimeException {
    public CustomException(String message) {
        super(message);
    }
}

3.2 自定义异常处理器

自定义的异常处理器可以使用@ExceptionHandler@ControllerAdvice来实现,这使我们可以有更多的控制权来定制异常的响应。

3.2.1 创建自定义异常处理器
@ControllerAdvice
public class CustomExceptionHandler {
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<String> handleCustomException(CustomException e) {
        return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

3.3 自定义错误响应

我们还可以定制异常的响应格式,例如,可以包含错误代码、错误消息、时间戳等,以提供更多的错误信息。

3.3.1 定义错误响应类
public class ErrorResponse {
    private int status;
    private String message;
    private long timestamp;
    
    // Constructors, getters and setters
}
3.3.2 返回自定义错误响应
@ControllerAdvice
public class CustomExceptionHandler {
    
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage(), System.currentTimeMillis());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
}

小结

通过自定义异常处理,我们可以构建出更加精确和灵活的异常处理策略,以满足特定的业务需求。自定义异常、异常处理器和错误响应允许我们全面掌控异常处理的每个环节,实现真正意义上的个性化异常处理。


状态码与异常

在Web应用中,HTTP状态码是服务端向客户端报告请求结果的一种重要方式。通过合适的状态码,服务端可以明确地告知客户端请求是成功还是失败,以及失败的原因。下面,我们将详细讨论如何在Spring中正确使用HTTP状态码来表示异常。

4.1 HTTP状态码概述

HTTP状态码由三位数字组成,其中第一位数字定义了状态码的类型。常见的状态码类型包括:

  • 2xx:成功。表示请求已被成功接收、理解和接受。
  • 4xx:客户端错误。表示客户端似乎有错误,例如,无效的请求或无法找到资源。
  • 5xx:服务器错误。表示服务器未能完成明显有效的请求。

4.2 状态码与异常的关系

在Spring中,我们通常使用ResponseEntity来表示HTTP响应,其中包含了状态码和响应体。当发生异常时,我们应该返回代表错误的状态码,如400 Bad Request500 Internal Server Error,并在响应体中提供错误的详细信息。

4.2.1 使用ResponseEntity返回状态码
@RestController
public class MyController {

    @GetMapping("/myEndpoint")
    public ResponseEntity<String> myEndpoint() {
        // ...
        return new ResponseEntity<>("Error Message", HttpStatus.BAD_REQUEST);
    }
}

4.3 使用@ResponseStatus定义状态码

@ResponseStatus注解允许我们在异常类或处理方法上直接指定HTTP状态码。当该异常被抛出时,Spring会自动使用指定的状态码作为HTTP响应的状态码。

4.3.1 在异常类上使用@ResponseStatus
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Resource not found")
public class ResourceNotFoundException extends RuntimeException {
}
4.3.2 在处理方法上使用@ResponseStatus
@RestController
public class MyController {

    @GetMapping("/myEndpoint")
    public String myEndpoint() {
        // ...
        throw new ResourceNotFoundException();
    }
    
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handleResourceNotFoundException() {
        return "Resource not found";
    }
}

小结

正确使用HTTP状态码可以使我们的应用更加符合HTTP协议,也使客户端更容易理解响应的含义。通过ResponseEntity@ResponseStatus,我们可以灵活地为异常指定合适的状态码,从而实现更加准确和清晰的错误报告。


异常处理的最佳实践

在进行异常处理时,遵循一些最佳实践可以帮助我们更有效、更准确地处理异常,从而提高软件的稳定性和用户体验。下面我们将探讨一些在Spring中进行异常处理的最佳实践。

5.1 准确的异常类型

选择准确的异常类型非常重要。应尽量避免抛出或处理过于宽泛的异常,例如ExceptionRuntimeException。更加具体的异常类型可以提供更多的上下文信息,有助于更准确地定位和处理问题。

5.2 自定义异常

当内置的异常类型无法准确描述问题时,可以考虑创建自定义异常。自定义异常应该继承自RuntimeException或其它更合适的异常基类,并提供足够的信息来描述异常场景。

public class CustomBusinessException extends RuntimeException {
    public CustomBusinessException(String message) {
        super(message);
    }
}

5.3 清晰准确的错误信息

错误信息应该清晰、准确且有助于理解问题。避免使用模糊或不准确的错误描述,应提供足够的信息帮助开发者和用户理解发生了什么问题。

5.4 妥善处理异常堆栈

异常堆栈是定位问题的重要信息。在开发和测试环境中,应该记录完整的异常堆栈。然而,在生产环境中,为了安全和用户体验,应该避免直接暴露详细的异常堆栈给用户。

5.5 使用HTTP状态码

如前所述,应该使用合适的HTTP状态码来表示响应的状态。例如,对于客户端的错误请求,应返回4xx状态码;对于服务器错误,应返回5xx状态码。

5.6 日志记录

确保所有的异常都被妥善记录,包括异常的类型、消息和堆栈。日志记录应该足够详细,以便于快速定位和解决问题。

5.7 测试异常处理

异常处理的代码也应该被充分测试。通过单元测试和集成测试来确保异常处理的逻辑正确无误,能够在实际发生异常时按照预期工作。

小结

异常处理是软件开发中的重要环节。遵循上述最佳实践,可以帮助开发者更有效地处理异常,减少软件的错误,并提高用户满意度。记住,清晰、准确的异常处理不仅可以减少开发的难度,也能够在软件出现问题时提供有效的帮助。


总结

在开发复杂的Spring应用程序时,异常处理是不可或缺的一环。合理而有效的异常处理不仅能够提高应用程序的健壮性和稳定性,还能够优化用户体验,减少开发和维护的难度。
在Spring中,有效的异常处理要求我们深入理解异常处理机制、策略和最佳实践。我们需要细心地设计和测试我们的异常处理逻辑,确保它们能够在实际运行中满足预期,为用户提供友好而准确的错误信息,同时也为开发者提供足够的信息来定位和解决问题。希望本文能够帮助读者更好地理解Spring中的异常处理,以及如何设计和实施有效的异常处理策略。


参考文献

  1. Spring、SpringBoot统一异常处理的3种方法 - CSDN
  2. Spring Boot 全局异常处理整理!开发必会! - 知乎
  3. Spring Boot项目优雅的全局异常处理方式(全网最新) - CSDN
  4. 基于Spring Cloud Gateway 的统一异常处理 - 掘金
  5. Spring Cloud 如何统一异常处理?写得太好了! - 腾讯云

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

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

相关文章

【设计模式】组合模式

文章目录 1.组合模式定义2.组合模式的结构2.1. 安全式组合模式的结构2.2.透明式组合模式的结构 3.组合模式实战案例3.1.场景说明3.2.关系类图3.3.代码实现 4.组合模式优缺点5.组合模式适用场景6.组合模式总结 主页传送门&#xff1a;&#x1f481; 传送 1.组合模式定义 组合模式…

Spring面试题8:面试官:说一说Spring的BeanFactory

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说Spring的BeanFactory Spring的BeanFactory是Spring框架的核心容器,负责管理和创建Bean对象。它是一个工厂类,用于实例化、配置和管理Bean的…

忽视日志吃大亏,手把手教你玩转 SpringBoot 日志

一、日志重要吗 程序中的日志重要吗&#xff1f; 在回答这个问题前&#xff0c;笔者先说个事例&#xff1a; ❝ 笔者印象尤深的就是去年某个同事&#xff0c;收到了客户反馈的紧急bug。尽管申请到了日志文件&#xff0c;但因为很多关键步骤没有打印日志&#xff0c;导致排查进…

基于springboot+vue的车辆管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

专业排版软件InDesign 2023下载 InDesign mac中文功能

InDesign 2023 mac是一款专业排版软件&#xff0c;适用于Windows和macOS平台。它可以帮助用户创建、设计和排版各种印刷品和数字出版物&#xff0c;如杂志、书籍、报纸、广告、海报、手册、电子书等。 InDesign 2023 mac软件特点 多种页面布局&#xff1a;支持多种页面布局&…

名义实际GDP-各地区-原始和结果(2000-2022年)

一、数据介绍 数据名称&#xff1a;名义、实际GDP-各地区-原始和结果 数据年份&#xff1a;2000-2022年 计算公式&#xff1a;实际GDP 名义GDP / GDP折算指数 数据基期&#xff1a;2000年 数据整理&#xff1a;自主整理 二、数据用途 数据用途 文献依据 经济发展水平 …

如何在.NET电子表格应用程序中创建流程图

前言 流程图是一种常用的图形化工具&#xff0c;用于展示过程中事件、决策和操作的顺序和关系。它通过使用不同形状的图标和箭头线条&#xff0c;将任务和步骤按照特定的顺序连接起来&#xff0c;以便清晰地表示一个过程的执行流程。 在企业环境中&#xff0c;高管和经理利用…

区间重叠问题

区间未重叠数量计算&#xff1a; class Solution {public int findMinArrowShots(int[][] points) {//需要用第一种比较器&#xff1f;第二种会报错&#xff0c;在涉及数的大小边界的时候Arrays.sort(points,(a, b) -> Integer.compare(a[0], b[0]));//(a, b) -> Intege…

【算法思想-排序】根据另一个数组次序排序 - 力扣 1122 题

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

员工执行力差,80%是领导的问题

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 读者小T是大厂P7&#xff0c;今年跳到一家行业独角兽公司&#xff0c;做项目经理。没想到&#xff0c;半年后他的领导Y总&#xff0c;在考核中给他的绩效打了D&#xff0c;主要原因是小T“项目按时完成…

大模型分布式训练策略:ZeRO、FSDP

文章目录 一、ZeRO&#xff08;零冗余优化器&#xff09;1.1 背景1.2 深度学习内存消耗分析1.3 主要方法1.3.1 ZeRO-DP优化模型状态内存1.3.2 ZeRO-R优化残余状态内存 1.4 总结1.5 官方视频&#xff1a;ZeRO & Fastest BERT&#xff0c;提高 DeepSpeed 深度学习训练的规模和…

ROS 基础教程

欢迎访问我的博客首页。 ROS 基础教程 1.urdf 文件1.1 在 Rviz 中显示 urdf1.1.1 定义 urdf1.1.2 在 Rviz 中查看 urdf 1.2 在 Gazebo 中显示 urdf1.2.1 定义 urdf1.2.2 在 Gazebo 中查看 urdf 2.建图-仿真2.1 模型 1.urdf 文件 假设我们的工作空间是 ws_ros。我们自己实现的包…

10路LED驱动器和GPIO控制器禾润HTR3310

供电范围&#xff1a;2.5V~5.5V 10个多功能IO&#xff0c;支持LED驱动或GPIO&#xff08;电流源调光&#xff09; LED模式下具有256阶线性调光 任意IO可配置为独立的输入或输出 中断功能&#xff0c;8μs防抖&#xff0c;低电平有效 标准I2C接口&#xff0c;4个I2C器件地址…

AB包的依赖关系

1、什么是依赖关系 有时候一个模型所需要的东西可能在不同的包里面&#xff0c;例如蓝色立方体的模型和材质在不同的包&#xff08;mode和head&#xff09;里&#xff0c;这时需要加载两个包才能让这个球正常显示 2、如何获取依赖关系并加载 //加载AB包 AssetBundle ab Asse…

a single dex file (# methods: 67938 > 65536)

问题 项目不大&#xff0c;但是导入的包比较多&#xff0c;导致方法数量超过了一定数量 Cannot fit requested classes in a single dex file (# methods: 67938 > 65536) Android 5.0之前的版本&#xff08;API level < 21&#xff09;使用Dalvik runtime来执行代码&a…

服务器搭建(TCP套接字)-epoll版(服务端)

epoll 是一种在 Linux 系统上用于高效事件驱动编程的 I/O 多路复用机制。它相比于传统的 select 和 poll 函数具有更好的性能和扩展性。 epoll 的主要特点和原理&#xff1a; 1、事件驱动&#xff1a;epoll 是基于事件驱动的模型&#xff0c;它通过监听事件来触发相应的回调函…

七分钟,数据转换器get到了

全文阅读时间 | 预计七分钟 KING BASE 开源 OR 闭源&#xff1f; 在瞬息多变的软件市场上&#xff0c;开源还是闭源是一个恒久不变的话题。开源软件得益于基础架构和基本功能的全面开放&#xff0c;开发者能自由使用和二次开发&#xff0c;但使用前提是需要投入大量成本对软件进…

centos 7.9系统安装向日葵

1.下载地址 向日葵远程控制app官方下载 - 贝锐向日葵官网 2.下载依赖 yum install -y libappindicator-gtk3 安装好依赖之后&#xff0c;然后再安装向日葵软件 3.安装软件 sudo rpm -ivh 文件名.rpm 4.安装成功之后的位置

【Android Framework系列】第16章 存储访问框架 (SAF)

1 概述 Android 4.4&#xff08;API 级别 19&#xff09;引入了存储访问框架 (Storage Access Framework)。SAF让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档、图像以及其他文件。 用户可以通过易用的标准 UI&#xff0c;以统一方式在所有应用和提供程序中浏…

【神印王座】龙皓晨竟然上了头版头条!内容违背,新闻真实性原则

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析神印王座国漫。 大家有没有发现&#xff0c;当龙皓晨他们从驱魔关回到圣城时&#xff0c;有这么一幕&#xff0c;一个卖报小孩边走边说&#xff1a;驱魔关大捷&#xff0c;少年英雄龙皓晨操控守护与怜悯之神印王座&#x…