Sprnig MVC 如何统一异常处理 (Exception Handling)?

news2025/12/17 2:24:20

主要有以下几种方式来实现统一异常处理,其中 @ControllerAdvice (或 @RestControllerAdvice) 结合 @ExceptionHandler 是最常用的方式。

1. @ExceptionHandler 注解

  • 作用: 用于标记一个方法,该方法将处理在同一个 Controller 类中抛出的特定类型的异常。

  • 怎么用:

    1. 在一个 Controller 类中,创建一个方法。
    2. 给这个方法添加 @ExceptionHandler 注解。
    3. 在注解中指定该方法要处理的异常类型 (一个或多个)。
    4. 方法的参数可以是:
      • 要处理的异常对象本身 (例如 NullPointerException ex)。
      • HttpServletRequest, HttpServletResponse, HttpSession
      • Model (如果你想返回一个视图并向模型中添加数据)。
    5. 方法的返回值可以是:
      • ModelAndView: 用于返回一个特定的错误视图,并可以携带错误信息。
      • String: 作为视图名。
      • @ResponseBody + 任何可被 HttpMessageConverter 转换的对象 (例如 ResponseEntity<ErrorResponse>, Map<String, Object>, 自定义错误对象): 用于返回JSON/XML等格式的错误响应体。
      • ResponseEntity<?>: 可以更灵活的控制响应状态码、头信息和响应体。
  • 示例 (在单个 Controller 内):

    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    @RequestMapping("/items")
    public class ItemController {
    
        @GetMapping("/{id}")
        public String getItem(@PathVariable String id) {
            if ("error".equals(id)) {
                throw new ItemNotFoundException("Item with id " + id + " not found!");
            }
            if ("npe".equals(id)) {
                String str = null;
                str.length(); // 会抛出 NullPointerException
            }
            return "itemDetails"; // 假设有一个 itemDetails.jsp 或 .html
        }
    
        // 处理当前Controller中抛出的ItemNotFoundException
        @ExceptionHandler(ItemNotFoundException.class)
        public ModelAndView handleItemNotFoundException(ItemNotFoundException ex) {
            ModelAndView mav = new ModelAndView("error/itemNotFound"); // 指向错误视图
            mav.addObject("errorMessage", ex.getMessage());
            mav.addObject("exceptionType", ex.getClass().getSimpleName());
            return mav;
        }
    
        // 处理当前Controller中抛出的NullPointerException,并返回JSON响应
        @ExceptionHandler(NullPointerException.class)
        @ResponseBody // 或者直接使用 @RestControllerAdvice 来替代
        public ResponseEntity<ErrorResponse> handleNullPointerException(NullPointerException ex, HttpServletRequest request) {
            ErrorResponse errorResponse = new ErrorResponse(
                    HttpStatus.INTERNAL_SERVER_ERROR.value(),
                    "A null pointer exception occurred.",
                    ex.getMessage(),
                    request.getRequestURI()
            );
            return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    
        // 处理其他未被特定处理的Exception (作为兜底)
        @ExceptionHandler(Exception.class)
        public ResponseEntity<String> handleGenericException(Exception ex) {
            return new ResponseEntity<>("An unexpected error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    
    // 自定义异常
    class ItemNotFoundException extends RuntimeException {
        public ItemNotFoundException(String message) {
            super(message);
        }
    }
    
    // 自定义错误响应体
    class ErrorResponse {
        private int status;
        private String message;
        private String details;
        private String path;
        // Constructors, Getters, Setters
        public ErrorResponse(int status, String message, String details, String path) {
            this.status = status;
            this.message = message;
            this.details = details;
            this.path = path;
        }
        // ... (getters and setters)
    }
    
  • 缺点: @ExceptionHandler 注解的方法只能处理其所在 Controller 类内部抛出的异常。如果想在多个 Controller 之间共享异常处理逻辑,这种方式就不够高效。

2. @ControllerAdvice@RestControllerAdvice 注解 (推荐的全局异常处理方式)

  • 作用:

    • @ControllerAdvice: 标记一个类,使其成为一个全局的 Controller 建言 (advice) 组件。这个类中的方法可以应用到应用程序中所有 (或指定的) Controller。
    • @RestControllerAdvice: 是 @ControllerAdvice@ResponseBody 的组合注解。它特别适用于构建 RESTful API,因为该类中所有 @ExceptionHandler 方法的返回值都会被自动转换为 HTTP 响应体 (通常是 JSON 或 XML)。
  • 怎么用:

    1. 创建一个普通的 Java 类。
    2. 给这个类添加 @ControllerAdvice@RestControllerAdvice 注解。
    3. 在这个类中,定义一个或多个使用 @ExceptionHandler 注解的方法,就像在单个 Controller 中那样。
    4. @ExceptionHandler 方法的参数和返回值规则与在单个 Controller 中使用时相同。
  • 示例 (全局异常处理):

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    import org.springframework.web.context.request.WebRequest; // 更通用的请求对象
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    @RestControllerAdvice // 因为我们想返回JSON/XML响应体
    // @ControllerAdvice // 如果你想返回ModelAndView或String作为视图名
    public class GlobalExceptionHandler {
    
        private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        // 处理自定义的 ItemNotFoundException
        @ExceptionHandler(ItemNotFoundException.class)
        @ResponseStatus(HttpStatus.NOT_FOUND) // 可以直接在这里设置响应状态码
        public ErrorResponse handleItemNotFoundException(ItemNotFoundException ex, WebRequest request) {
            logger.error("ItemNotFoundException occurred: {}", ex.getMessage(), ex);
            return new ErrorResponse(
                    HttpStatus.NOT_FOUND.value(),
                    "Resource Not Found",
                    ex.getMessage(),
                    request.getDescription(false) // 获取请求路径,不包含查询参数
            );
        }
    
        // 处理参数校验异常 (例如 @Valid 注解失败)
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public Map<String, Object> handleValidationExceptions(MethodArgumentNotValidException ex, WebRequest request) {
            logger.warn("Validation error: {}", ex.getMessage());
            Map<String, Object> response = new HashMap<>();
            response.put("timestamp", new Date());
            response.put("status", HttpStatus.BAD_REQUEST.value());
            response.put("error", "Validation Failed");
            response.put("path", request.getDescription(false));
            // 获取所有字段的校验错误信息
            Map<String, String> errors = ex.getBindingResult()
                    .getFieldErrors()
                    .stream()
                    .collect(Collectors.toMap(
                            fieldError -> fieldError.getField(),
                            fieldError -> fieldError.getDefaultMessage() != null ? fieldError.getDefaultMessage() : "Invalid value"
                    ));
            response.put("errors", errors);
            return response;
        }
    
        // 处理其他所有未被特定处理的Exception (作为全局兜底)
        @ExceptionHandler(Exception.class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ErrorResponse handleAllUncaughtException(Exception ex, WebRequest request) {
            logger.error("An unexpected error occurred: {}", ex.getMessage(), ex);
            return new ErrorResponse(
                    HttpStatus.INTERNAL_SERVER_ERROR.value(),
                    "Internal Server Error",
                    "An unexpected error occurred. Please try again later.",
                    request.getDescription(false)
            );
        }
    }
    
    // 假设 ItemNotFoundException 和 ErrorResponse 类定义同上
    
  • @ControllerAdvice 的属性 (用于限定范围):

    • valuebasePackages: 指定该 ControllerAdvice 应用的包。例如 @ControllerAdvice("com.example.api.controllers")
    • annotations: 指定该 ControllerAdvice 应用于带有特定注解的 Controller。例如 @ControllerAdvice(annotations = RestController.class)
    • assignableTypes: 指定该 ControllerAdvice 应用于特定类型的 Controller。例如 @ControllerAdvice(assignableTypes = {MyBaseController.class})

3. 实现 HandlerExceptionResolver 接口 (更底层,不常用)

  • 这是一个更底层的接口,允许你创建自定义的异常解析策略。
  • 你需要实现 resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法。
  • Spring MVC 默认提供了几个实现,如 ExceptionHandlerExceptionResolver (处理 @ExceptionHandler 注解)、ResponseStatusExceptionResolver (处理带有 @ResponseStatus 注解的异常)、DefaultHandlerExceptionResolver (处理Spring MVC内部的一些标准异常)。
  • 通常情况下,使用 @ControllerAdvice@ExceptionHandler 已经足够了,不需要直接实现此接口。

4. 使用 @ResponseStatus 注解标记自定义异常类

  • 你可以直接在自定义异常类上使用 @ResponseStatus 注解来指定当该异常被抛出且未被 @ExceptionHandler 捕获时,应该返回的 HTTP 状态码。

    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "The requested resource was not found.")
    class ResourceNotFoundException extends RuntimeException {
        public ResourceNotFoundException(String message) {
            super(message);
        }
    }
    

    ResourceNotFoundException 抛出且没有被更具体的 @ExceptionHandler 处理时,Spring MVC (通过 ResponseStatusExceptionResolver) 会自动返回 404 状态码,并将 reason 作为响应体(如果 reason 存在)。

统一异常处理的实现步骤总结 (使用 @RestControllerAdvice@ExceptionHandler):

  1. 创建自定义异常类 (可选但推荐):

    • 根据业务需求创建具体的异常类,继承自 RuntimeExceptionException
    • 例如: UserNotFoundException, InvalidInputException, OrderProcessingException
  2. 创建全局异常处理类:

    • 创建一个类,并使用 @RestControllerAdvice (用于REST API) 或 @ControllerAdvice (用于传统MVC) 注解。
  3. 在全局异常处理类中定义 @ExceptionHandler 方法:

    • 为每种你想要特殊处理的异常类型(包括自定义异常和Spring内置异常如 MethodArgumentNotValidException)创建一个方法。
    • 在方法上使用 @ExceptionHandler(YourExceptionClass.class) 注解。
    • 在方法体中,构造并返回一个统一的错误响应对象 (例如一个包含状态码、错误消息、详细信息、时间戳等的POJO)。
    • 可以使用 @ResponseStatus 注解在方法级别直接指定HTTP状态码,或者在 ResponseEntity 中设置。
  4. 定义统一的错误响应结构 (POJO):

    • 创建一个类来表示标准的错误响应格式,例如前面示例中的 ErrorResponse。这样可以确保所有错误响应都有一致的结构。
  5. (可选) 配置日志记录:

    • @ExceptionHandler 方法中,使用日志框架 (如 SLF4J + Logback/Log4j2) 记录异常的详细信息,包括堆栈跟踪,调试非常重要。

通过上述方式,我们可以将异常处理逻辑从业务代码中分离出来,使Controller 代码更简洁,并且能够为客户端提供友好的错误反馈。

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

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

相关文章

SpringAI-RC1正式发布:移除千帆大模型!

续 Spring AI M8 版本之后&#xff08;5.1 发布&#xff09;&#xff0c;前几日 Spring AI 悄悄的发布了最新版 Spring AI 1.0.0 RC1&#xff08;5.13 发布&#xff09;&#xff0c;此版本也将是 GA&#xff08;Generally Available&#xff0c;正式版&#xff09;发布前的最后…

操作系统之进程和线程听课笔记

计算机的上电运行就是构建进程树,进程调度就是在进程树节点进程进行切换 进程间通信的好处 经典模型 生产者和消费者 进程和线程的区别 线程引入带来的问题线程的优势 由于unix70年代产生,90年代有线程,当时数据库系统操作需要线程,操作系统没有来得及重造,出现了用户态线…

COMSOL随机参数化表面流体流动模拟

基于粗糙度表面的裂隙流研究对于理解地下水的流动、污染物传输以及与之相关的地质灾害&#xff08;如滑坡&#xff09;等方面具有重要意义。本研究通过蒙特卡洛方法生成随机表面形貌&#xff0c;并利用COMSOL Multiphysics对随机参数化表面的微尺度流体流动进行模拟。 参数化…

JavaSwing中的容器之--JScrollPane

JavaSwing中的容器之–JScrollPane 在Java Swing中&#xff0c;容器是用于容纳其他组件&#xff08;如按钮、标签等&#xff09;的组件。Swing提供了多种容器&#xff0c;它们可以嵌套使用以创建复杂的用户界面。 JScrollPane是一个轻量级组件&#xff0c;提供可滚动视图。JSc…

使用 Cookie 实现认证跳转功能

使用 Cookie 实现认证跳转功能的实践与解析 在 Web 开发中&#xff0c;用户身份认证是一个基础而关键的功能点。本文将通过一个简单的前后端示例系统&#xff0c;介绍如何基于 Cookie 实现 Token 保存与自动跳转认证的功能&#xff0c;并结合 Cookie 与 Header 的区别、使用场…

LED接口设计

一个LED灯有3种控制状态&#xff0c;常亮、常灭和闪烁&#xff0c;要做到这种控制最简单的一种方法是使用任何一款处理器的普通IO去控制。 用IO控制方式有两种&#xff0c;一种是高有效&#xff0c;如下图1所示IO口为高电平时LED亮&#xff0c;IO为低电平时LED不亮。IO口出一个…

SpringBoot项目使用POI-TL动态生成Word文档

近期项目工作需要动态生成Word文档的需求&#xff0c;特意调研了动态生成Word的技术方案。主要有以下两种&#xff1a; 第一种是FreeMarker模板来进行填充&#xff1b;第二种是POI-TL技术使用Word模板来进行填充&#xff1b; 以下是关于POI-TL的官方介绍 重点关注&#xff1…

YOLOv3深度解析:多尺度特征融合与实时检测的里程碑

一、YOLOv3的诞生&#xff1a;继承与突破的起点 YOLOv3作为YOLO系列的第三代算法&#xff0c;于2018年由Joseph Redmon等人提出。它在YOLOv2的基础上&#xff0c;针对小目标检测精度低、多类别标签预测受限等问题进行了系统性改进。通过引入多尺度特征图检测、残差网络架构和独…

uniapp-商城-60-后台 新增商品(属性的选中和页面显示)

前面添加了属性&#xff0c;添加属性的子级项目。也分析了如何回显&#xff0c;但是在添加新的商品的时&#xff0c;我们也同样需要进行选择&#xff0c;还要能正常的显示在界面上。下面对页面的显示进行分析。 1、界面情况回顾 属性显示其实是个一嵌套的数据显示。 2、选中的…

虹科技术 | 简化汽车零部件测试:LIN/CAN总线设备的按键触发功能实现

汽车零部件测试领域对操作的便捷性要求越来越高&#xff0c;虹科Baby-LIN-RC系列产品为这一需求提供了完美的解决方案。从基础的按键设置到高级的Shift键应用&#xff0c;本文将一步步引导您了解虹科Baby-LIN-RC系列产品的智能控制之道。 虹科Baby-LIN-3-RC 想象一下&#xff0…

单片机ESP32天气日历闹铃语音播报

自制Arduino Esp32 单片机 可以整点语音播报&#xff0c;闹铃语音播报&#xff0c;农历显示&#xff0c;白天晚上天气&#xff0c;硬件有 Esp32&#xff0c;ST7789显示屏&#xff0c;Max98357 喇叭驱动&#xff0c;小喇叭一枚。有需要源码的私信我。#单片机 #闹钟 #嵌入式 #智能…

如何解决LCMS 液质联用液相进样器定量环漏液问题

以下是解决安捷伦1260液相色谱仪为例的进样器定量环漏液问题的一些方法&#xff1a;视频操作 检查相关部件 检查定量环本身&#xff1a;观察定量环是否有破损、裂纹或变形等情况。如果发现定量环损坏&#xff0c;需及时更换。检查密封垫&#xff1a;查看进样阀的转子密封垫、计…

服务器内部可以访问外部网络,docker内部无法访问外部网络,只能docker内部访问

要通过 iptables 将容器中的特定端口请求转发到特定服务器&#xff0c;你需要设置 DNAT&#xff08;目标地址转换&#xff09;规则。以下是详细步骤&#xff1a; 假设场景 容器端口: 8080&#xff08;容器内服务监听的端口&#xff09;目标服务器: 192.168.1.100&#xff08;请…

PCIe Switch 问题点

系列文章目录 文章目录 系列文章目录完善PCIe Retimer Overview Document OutlineSwitch 维度BroadComMicroChipAsmedia 祥硕Cyan其他 完善 Functional block diagram&#xff0c;功能框图Key Features and Benefits&#xff0c;主要功能和优点Fabric 链路Multi-root PCIe Re…

开源轻量级地图解决方案leaflet

Leaflet 地图&#xff1a;开源轻量级地图解决方案 Leaflet 是一个开源的 JavaScript 库&#xff0c;用于在网页中嵌入交互式地图。它以轻量级、灵活性和易用性著称&#xff0c;适用于需要快速集成地图功能的项目。以下是关于 Leaflet 的详细介绍和使用指南。 1. Leaflet 的核心…

Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件

目录 1. 创建Flutter项目 1.1使用Android Studio创建Flutter项目 1.2 使用命令行创建Flutter项目 2. Flutter项目介绍 2.1所有代码都在lib目录下编写 2.1 pubspec.yaml 依赖库/图片的引用 ​编辑 3. 运行项目 4. 编写mian.dart文件 4.1 使用MaterialApp 和 Scaffold两个组件…

如何实现金蝶云星空到MySQL的数据高效集成

金蝶云星空数据集成到MySQL的技术案例分享 在企业信息化建设中&#xff0c;数据的高效流动和准确处理是关键。本文将聚焦于一个具体的系统对接集成案例&#xff1a;金蝶云星空的数据集成到MySQL&#xff0c;方案名称为“xsck-2金蝶销售出库-->mysql”。通过这一案例&#x…

院校机试刷题第四天:1911反转公约数、1702十六进制不进位加法

一、1911反转公约数 1.题目描述 2.解题思路 两个关键点&#xff1a;1.如何把数字反转&#xff0c;2.如何求最大公约数。 反转&#xff1a;用字符串形式存储&#xff0c;定义一个新的字符串倒序存储反转之后的字符串&#xff0c;将字符串按位转换位数字。 求最大公约数&…

Redis解析

Redis解析 一、单线程模型 redis在io层面是多线程的&#xff0c;在数据处理层面是单线程的。 多线程一般用于&#xff1a; 关闭连接删除/淘汰内存网络IO 1.1 io多路复用 redis使用nio&#xff08;select、poll、epoll&#xff09;的方式处理socket 主线程负责接收建立连接…

2025年Ai写PPT工具推荐,这5款Ai工具可以一键生成专业PPT

上个月给客户做产品宣讲时&#xff0c;我对着空白 PPT 页面熬到凌晨一点&#xff0c;光是调整文字排版就改了十几版&#xff0c;最后还是被吐槽 "内容零散没重点"。后来同事分享了几款 ai 写 PPT 工具&#xff0c;试完发现简直打开了新世界的大门 —— 不用手动写大纲…