SpringBoot使用@ControllerAdvice进行统一处理异常详解

 更新时间:2026年01月07日 08:38:46   作者:程序员大华  
在实际开发中,程序运行时难免会遇到各种意外,如果不做统一处理,用户可能会看到一堆错误信息,下面我们就来看看SpringBoot如何使用@ControllerAdvice进行统一处理异常吧

一、什么是全局异常处理?

在实际开发中,程序运行时难免会遇到各种意外。这些意外在 Java 中就表现为异常(Exception)。如果不做统一处理,用户可能会看到一堆错误信息。

常见场景

  • NullPointerException(空指针异常):前端传了一个用户 ID,但后端没做判空,直接调用 user.getName(),结果 user 是 null 导致程序崩溃!
  • SQLException(数据库连接失败):数据库宕机或网络中断,导致查询失败,直接抛出 SQL 异常。
  • NumberFormatException(参数格式错误):前端传了个字符串 "abc",后端试图用 Integer.parseInt() 转成数字失败!

如果不做处理,会发生什么

SpringBoot 默认会把异常的完整堆栈信息原样返回给前端,比如:

{
  "timestamp": "2025-11-26T12:00:00",
  "status": 500,
  "error": "Internal Server Error",
  "message": "/ by zero",
  "path": "/api/calculate"
}

甚至在开发环境下,还可能返回几十行 Java 堆栈,这些会导致以下以下问题:

  • 用户看不懂,
  • 前端无法统一处理,
  • 更严重的是:可能泄露类名、方法名、服务器路径等敏感信息!

有了全局异常处理,就能

  • 拦截所有异常,统一返回结构化错误信息
  • 区分不同异常类型,返回不同的状态码和提示语
  • 自动记录日志,方便排查问题
  • 避免敏感信息外泄,提升系统安全性

最终返回给前端的,可能是这样一段干净、友好的 JSON:

{
  "code": 400,
  "message": "用户ID不能为空",
  "data": null
}

或者:

{
  "code": 200,
  "message": "订单创建成功",
  "data": true
}

是不是清爽又专业多了

二、如何做异常处理

传统做法(不推荐)

每个 Controller 方法里都写 try-catch

@GetMapping("/user/{id}")
public ResponseEntity getUser(@PathVariable Long id) {
    try {
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    } catch (UserNotFoundException e) {
        return ResponseEntity.status(404).body("用户不存在");
    } catch (Exception e) {
        return ResponseEntity.status(500).body("系统错误");
    }
}

问题很明显:

  • 代码重复
  • 难维护
  • 容易漏掉

全局异常处理(推荐)

只写一次,全局生效!

下面主要写 SpringBoot 的处理。

三、SpringBoot 中如何实现全局异常处理?

核心就靠一个注解:@ControllerAdvice

第一步:定义统一的错误响应格式

// 统一返回结构,前端好解析
public class ErrorResponse {
    private int code;
    private String message;
    private Object data; // 一般为 null,但保留扩展性

    public ErrorResponse(int code, String message) {
        this.code = code;
        this.message = message;
    }

    // getter / setter 省略(实际项目中建议用 Lombok)
}

第二步:创建全局异常处理器类

// 1. 创建一个类,加上 @ControllerAdvice 注解
@ControllerAdvice
public class GlobalExceptionHandler {

    // 2. 用 @ExceptionHandler 捕获特定异常
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity handleUserNotFound(UserNotFoundException e) {
        ErrorResponse error = new ErrorResponse(404, e.getMessage());
        return ResponseEntity.status(404).body(error);
    }

    // 3. 捕获所有其他异常(兜底)
    @ExceptionHandler(Exception.class)
    public ResponseEntity handleGenericException(Exception e) {
        ErrorResponse error = new ErrorResponse(500, "服务器内部错误,请联系管理员");
        return ResponseEntity.status(500).body(error);
    }
}

第三步:自定义业务异常(可选但推荐)

// 自定义业务异常
public class BusinessException extends RuntimeException {
    private Integer code;
    
    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    
    // getter省略
}

然后在 Service 层这样用:

public User findById(Long id) {
    if (id == null || id <= 0) {
        throw new BusinessException(&#34;用户ID无效&#34;);
    }
    // 查询数据库...
}

可以说配置全局异常是比较简单的,可能因为简单,或者是项目里本来就配置的有,所以很多朋友直接忽略掉了这些问题。

四、更智能的异常处理

1. 区分 Controller 和 REST 接口

如果你既有页面(返回 HTML),又有 API(返回 JSON),可以用:

@ControllerAdvice(annotations = RestController.class)
public class RestGlobalExceptionHandler {
    // 只处理 @RestController 的异常
}

或者指定包路径:

@ControllerAdvice(basePackages = &#34;com.example.api&#34;)
public class ApiExceptionHandler { ... }

2. 处理参数校验异常(如 @Valid)

SpringBoot 自带校验框架,校验失败会抛 MethodArgumentNotValidException

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleValidation(MethodArgumentNotValidException e) {
    String msg = e.getBindingResult().getFieldError().getDefaultMessage();
    return ResponseEntity.badRequest().body(new ErrorResponse(400, msg));
}

3. 记录日志(非常重要!)

handleGenericException 中加入日志:

private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(Exception.class)
public ResponseEntity handleGenericException(Exception e) {
    log.error(&#34;系统发生未预期异常&#34;, e); // 记录完整堆栈
    return ResponseEntity.status(500).body(new ErrorResponse(500, &#34;服务器开小差啦~&#34;));
}

五、深入理解@ControllerAdvice

很多同学可能会好奇:为什么加了@ControllerAdvice后,异常就能被自动捕获呢?

其实原理很简单:

  • Spring MVC 在处理请求时,如果 Controller 方法抛出异常
  • Spring 会查找有没有对应的异常处理器(@ExceptionHandler
  • 查找顺序是:先找当前 Controller 中的 @ExceptionHandler 方法
  • 如果当前 Controller 没有,就找 @ControllerAdvice 标注的类中的 @ExceptionHandler 方法
  • 找到匹配的异常处理器后,调用对应的方法处理异常

最佳实践建议

1. 定义清晰的异常体系

// 基础异常类
public class BaseException extends RuntimeException {
    private Integer code;
    
    public BaseException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

// 各种具体异常
public class BusinessException extends BaseException {
    public BusinessException(Integer code, String message) {
        super(code, message);
    }
}

public class SystemException extends BaseException {
    public SystemException(Integer code, String message) {
        super(code, message);
    }
}

2. 使用枚举管理错误码

public enum ErrorCode {
    SUCCESS(200, &#34;成功&#34;),
    PARAM_ERROR(400, &#34;参数错误&#34;),
    UNAUTHORIZED(401, &#34;未授权&#34;),
    FORBIDDEN(403, &#34;禁止访问&#34;),
    NOT_FOUND(404, &#34;资源不存在&#34;),
    SYSTEM_ERROR(500, &#34;系统错误&#34;);
    
    private final Integer code;
    private final String message;
    
    ErrorCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    
    // getter方法
}

3. 日志记录要恰当

  • 业务异常:一般用warn级别,不需要记录堆栈
  • 系统异常:用error级别,需要记录堆栈信息

完整示例代码

// 统一返回结果
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
    private Integer code;
    private String message;
    private T data;
    
    public static  Result success(T data) {
        return new Result<>(200, &#34;成功&#34;, data);
    }
    
    public static  Result error(Integer code, String message) {
        return new Result<>(code, message, null);
    }
}

// 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public Result handleBusinessException(BusinessException e) {
        logger.warn(&#34;业务异常:code={}, message={}&#34;, e.getCode(), e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }
    
    // 处理参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(&#34;, &#34;));
        return Result.error(400, message);
    }
    
    // 处理所有其他异常
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        logger.error(&#34;系统异常:&#34;, e);
        return Result.error(500, &#34;系统繁忙,请稍后重试&#34;);
    }
}

总结

统一格式:前后端约定好错误结构,沟通更顺畅

减少重复代码:不用每个方法写 try-catch

提升健壮性:兜底处理防止系统崩溃,还能记录日志

到此这篇关于SpringBoot使用@ControllerAdvice进行统一处理异常详解的文章就介绍到这了,更多相关SpringBoot异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java使用ZipInputStream实现读取和写入zip文件

    java使用ZipInputStream实现读取和写入zip文件

    zip文档可以以压缩格式存储一个或多个文件,本文主要为大家详细介绍了java如何使用ZipInputStream读取Zip文档与写入,需要的小伙伴可以参考下
    2023-11-11
  • java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径

    java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径

    这篇文章主要介绍了java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径,需要的朋友可以参考下
    2014-04-04
  • JavaEE中关于ServletConfig的小结

    JavaEE中关于ServletConfig的小结

    ServletConfig是针对特定的Servlet的参数或属性。ServletConfig是表示单独的Servlet的配置和参数,只是适用于特定的Servlet。从一个servlet被实例化后,对任何客户端在任何时候访问有效,但仅对本servlet有效,一个servlet的ServletConfig对象不能被另一个servlet访问
    2014-10-10
  • springboot如何根据配置屏蔽接口返回字段

    springboot如何根据配置屏蔽接口返回字段

    这篇文章主要介绍了springboot如何根据配置屏蔽接口返回字段问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • 浅谈cookie 和session 的区别

    浅谈cookie 和session 的区别

    下面小编就为大家带来一篇浅谈cookie 和session 的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • Java实现多个文档合并输出到一个文档

    Java实现多个文档合并输出到一个文档

    这篇文章主要为大家详细介绍了Java实现多个文档合并输出到一个文档的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • Java可重入锁ReentrantLock详解

    Java可重入锁ReentrantLock详解

    这篇文章主要介绍了Java可重入锁ReentrantLock详解,ReentrantLock是一个可重入且独占式的锁,是一种递归无阻塞的同步机制,它支持重复进入锁,即该锁能够支持一个线程对资源的重复加锁,除此之外,该锁的还支持获取锁时的公平和非公平性选择,需要的朋友可以参考下
    2023-09-09
  • JAVA三种异常处理机制的具体使用

    JAVA三种异常处理机制的具体使用

    异常是程序在编译或执行的过程中可能出现的问题,本文主要介绍了JAVA三种异常处理机制的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06
  • 详解Java的Enum的使用与分析

    详解Java的Enum的使用与分析

    这篇文章主要介绍了详解Java的Enum的使用与分析的相关资料,需要的朋友可以参考下
    2017-02-02
  • Java+OpenCV实现图片中的人脸识别

    Java+OpenCV实现图片中的人脸识别

    这篇文章主要介绍了如何利用java opencv实现人脸识别功能,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03

最新评论