@ControllerAdvice之全局异常处理过程

 更新时间:2025年11月05日 09:37:46   作者:Tech_Jia_Hui  
文章介绍了在Java中处理异常的两种方式:就地解决和向上抛出,并重点介绍了Spring框架中的@ControllerAdvice注解,用于实现全局异常处理,从而避免了在每个Controller类中重复编写try-catch代码块,这种方法不仅提高了代码的可维护性,还使得异常处理逻辑的修改更加方便

背景

在业务逻辑中存在大量重复的 try-catch 代码块(且没有全局异常处理机制)会带来以下显著问题:

  • 每个方法都需要手动编写 try-catch,导致大量重复代码,违反 DRY(Don’t Repeat Yourself)原则。
  • 如果异常处理逻辑需要调整(例如日志格式、错误码规范),必须逐个修改所有 try-catch 块,容易遗漏或出错。
public void processOrder() {
    try {
        // 业务逻辑
    } catch (OrderException e) {
        log.error("订单处理失败: {}", e.getMessage());
    }
}

public void refundOrder() {
    try {
        // 业务逻辑
    } catch (RefundException e) {
        log.error("退款失败: {}", e.getMessage());
    }
}

java中的异常处理方式

1、就地解决

即用try-catch块包裹住容易发生异常的代码片段,这样当该代码片段真正发生异常后,就会立即被catch块捕捉并在catch块中处理,从而使代码继续往下执行,不影响其他代码的执行。

2、向上抛出

如果不想立马就地处理,可以选择将该异常向上抛出,让方法的调用者处理。若方法的调用者也不行处理,同样可以继续向上抛出该异常,以此类推,直到将该异常抛给JVM处理。可是JVM懒啊,一看你们该处理的都不处理是吧,好,我也不处理,我还要把你们方法的调用过程给晒出来,结果就可以在控制台看到方法调用的堆栈信息了。

为什么要使用全局异常处理

总是用try-catch块包裹代码块也不好,影响性能不说代码看着不是很美观,所以我们选择用第二种——向上抛出的方式。向上抛出没问题,但总要有一个地方处理该异常,在web系统当中还应该将该异常以一个用户可以接受的方式返回给前端,不但在接口对接的时候让前端小姐姐知道是我们后台接口出现了问题,不至于摸不着头脑;而且我们后端开发人员也能根据接口返回的结果快速的知道到底是哪里出现了问题,才能快速解决问题。

全局异常处理

Spring在3.2版本增加了一个注解@ControllerAdvice,可以与@ExceptionHandler、@InitBinder、@ModelAttribute 等注解注解配套使用。

不过跟异常处理相关的只有注解@ExceptionHandler(该注解是springmvc中的一个注解)

该注解的作用:它通常用在控制器(Controller)类的方法上,以指定该方法用于处理特定类型的异常。当控制器中的其他方法抛出该异常时,Spring 会自动调用带有 @ExceptionHandler 注解的方法来处理该异常。

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

// 这是一个简单的控制器类
@RestController
public class MyController {

    // 一个可能会抛出异常的方法
    public String doSomething() {
        // 假设这里有一些逻辑,可能会抛出 IllegalArgumentException
        if (true) { // 这里只是为了演示,实际上应该有具体的逻辑判断
            throw new IllegalArgumentException("Invalid argument provided");
        }
        return "Success";
    }

    // 使用 @ExceptionHandler 来处理 IllegalArgumentException
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        // 创建一个包含错误信息的响应实体
        return new ResponseEntity<>("Invalid argument: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}
在上面的例子中,doSomething 方法可能会抛出一个 IllegalArgumentException。当这个异常被抛出时,Spring 会自动调用 handleIllegalArgumentException 方法来处理它,并返回一个包含错误信息的 ResponseEntity 对象,其 HTTP 状态码为 400 Bad Request。

但是,这样一来,就必须在每一个Controller类都定义一套这样的异常处理方法,因为异常可以是各种各样。这样一来,就会造成大量的冗余代码,而且若需要新增一种异常的处理逻辑,就必须修改所有Controller类了,很不优雅。

当然你可能会说,那就定义个类似BaseController的基类,这样总行了吧。

这种做法虽然没错,但仍不尽善尽美,因为这样的代码有一定的侵入性和耦合性。简简单单的Controller,我为啥非得继承这样一个类呢,万一已经继承其他基类了呢。大家都知道Java只能继承一个类。

那有没有一种方案,既不需要跟Controller耦合,也可以将定义的 异常处理器 应用到所有控制器呢?所以注解@ControllerAdvice出现了,简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独一个类,定义一套对各种异常的处理机制,然后在类的签名加上注解@ControllerAdvice,统一对 不同阶段的、不同异常 进行处理。这就是统一异常处理的原理。

@ControllerAdvice使用

1、创建 MyControllerAdvice.java

如下:

package com.sam.demo.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * controller 增强器
 *
 * @author sam
 * @since 2017/7/17
 */
@ControllerAdvice
public class MyControllerAdvice {

    /**
     * 全局异常捕捉处理
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Map errorHandler(Exception ex) {
        Map map = new HashMap();
        map.put("code", 100);
        map.put("msg", ex.getMessage());
        return map;
    }
    
    /**
     * 拦截捕捉自定义异常 MyException.class
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MyException.class)
    public Map myErrorHandler(MyException ex) {
        Map map = new HashMap();
        map.put("code", ex.getCode());
        map.put("msg", ex.getMsg());
        return map;
    }

}

2、controller中抛出异常进行测试

@RequestMapping("/home")
public String home() throws Exception {

//        throw new Exception("Sam 错误");
    throw new MyException("101", "Sam 错误");

}

启动应用,访问:http://localhost:8080/home ,正常显示以下json内容,证明自定义异常已经成功被拦截。

{"msg":"Sam 错误","code":"101"}

@ControllerAdvice的原理

1、组件扫描与注册

  • 注解标记:当类被 @ControllerAdvice 标注时,Spring 会在启动时通过组件扫描(Component Scan)发现该类。
  • Bean 注册:将该类注册为特殊的全局组件,其优先级高于普通 Controller,但低于具体的 Controller 方法。

2、异常处理流程

当 Controller 方法抛出异常时,Spring MVC 的处理流程如下:

  • 异常抛出:Controller 方法执行中抛出异常(如 throw new ServiceException())。
  • 异常传播:异常被 Spring 的 DispatcherServlet 捕获。
  • 查找处理器:Spring 遍历所有 @ControllerAdvice 类中的 @ExceptionHandler 方法,寻找与异常类型匹配的方法。
    执行处理:调用匹配的 @ExceptionHandler 方法生成响应(如返回 JSON 错误信息)。
  • 返回响应:将处理结果返回客户端,中断原始请求流程。
@ControllerAdvice
public class GlobalExceptionHandler {

    // 处理特定异常
    @ExceptionHandler(ServiceException.class)
    @ResponseBody
    public ResponseResult<Void> handleServiceException(ServiceException e) {
        return ResponseResult.fail(e.getCode(), e.getMessage());
    }

    // 处理所有未捕获的异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseResult<Void> handleException(Exception e) {
        return ResponseResult.fail(500, "系统繁忙");
    }
}

3、优先级规则

  • 精确匹配优先:@ExceptionHandler 方法定义的异常类型越具体,优先级越高。
  • Controller 内优先:若 Controller 内部定义了 @ExceptionHandler,则优先于全局 @ControllerAdvice。
  • 多个 @ControllerAdvice 类:通过 @Order 注解指定优先级,数值越小优先级越高。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 详解Spring AOP 拦截器的基本实现

    详解Spring AOP 拦截器的基本实现

    本篇文章主要介绍了详解Spring AOP 拦截器的基本实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • Java 中的CharArrayReader 介绍_动力节点Java学院整理

    Java 中的CharArrayReader 介绍_动力节点Java学院整理

    CharArrayReader 是字符数组输入流。它和ByteArrayInputStream类似,只不过ByteArrayInputStream是字节数组输入流,而CharArray是字符数组输入流。CharArrayReader 是用于读取字符数组,它继承于Reader
    2017-05-05
  • 解决spring-boot-maven-plugin报红的问题

    解决spring-boot-maven-plugin报红的问题

    这篇文章主要给大家介绍一下如何解决spring-boot-maven-plugin报红的问题,文中通过图文讲解的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2023-08-08
  • 详解Java中运算符及用法

    详解Java中运算符及用法

    这篇文章主要介绍了详解Java中运算符以及相关的用法讲解,一起跟着小编学习下吧。
    2017-12-12
  • Java 深入探讨设计模式之原型模式篇

    Java 深入探讨设计模式之原型模式篇

    设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性
    2021-10-10
  • Java使用OpenOffice将office文件转换为PDF的示例方法

    Java使用OpenOffice将office文件转换为PDF的示例方法

    OpenOffice是一个开源的办公套件,它包含了文档处理、电子表格、演示文稿以及绘图等多种功能,类似于Microsoft Office,本文将给大家介绍Java使用OpenOffice将office文件转换为PDF的示例方法,需要的朋友可以参考下
    2024-09-09
  • springboot访问不存在的URL时的处理方法

    springboot访问不存在的URL时的处理方法

    在前后端分离的模式下,当Spring Boot应用接收到一个不存在的URL请求时,通常希望返回一个固定的JSON字符串作为响应,以便前端能够据此进行相应的处理,本文给大家介绍了springboot访问不存在的URL时的处理方法,需要的朋友可以参考下
    2024-12-12
  • Java CountDownLatch应用场景代码实例

    Java CountDownLatch应用场景代码实例

    这篇文章主要介绍了Java CountDownLatch应用场景代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Springboot如何连接远程服务器上的数据库实践

    Springboot如何连接远程服务器上的数据库实践

    本文主要介绍了Springboot如何连接远程服务器上的数据库实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • JDK1.6“新“特性Instrumentation之JavaAgent(推荐)

    JDK1.6“新“特性Instrumentation之JavaAgent(推荐)

    这篇文章主要介绍了JDK1.6“新“特性Instrumentation之JavaAgent,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08

最新评论