springboot全局异常处理器处理顺序是什么?

新手上路,请多包涵

springboot全局异常处理器处理顺序问题
在使用异常处理器时,代码


package com.ecode.handler;


import com.ecode.constant.MessageConstant;
import com.ecode.exception.BaseException;
import com.ecode.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.sql.SQLException;

/**
 * 全局异常处理器,处理项目中抛出的业务异常
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("业务异常:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }

    /**
     * @param ex 数据库异常
     * @return 后端统一返回结果
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLException ex){
        String message = ex.getMessage();
        log.error("数据库异常:{}",message);
        if (message.contains("Duplicate entry")){
            String username = message.split(" ")[2];
            return Result.error(username + MessageConstant.ALREADY_EXISTS);
        }else {
            return Result.error(MessageConstant.SQL_UNKNOWN_ERROR);
        }
    }

    @ExceptionHandler
    public Result exceptionHandler(Exception ex){
        log.error("服务器异常:{}",ex.getMessage());
        return Result.error("服务器异常");

    }

}

当出现数据库异常时,返回

2024-10-05 22:56:13.798 ERROR 26216 --- [nio-8080-exec-4] c.ecode.handler.GlobalExceptionHandler   : 服务器异常:
### Error updating database.  Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'xu*****@foxmail.com' for key 'user.邮箱'
### The error may exist in com/ecode/mapper/UserMapper.java (best guess)
### The error may involve com.ecode.mapper.UserMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO user  ( username, password,  email, status, name, profile_picture,    score,  create_time, update_time )  VALUES  ( ?, ?,  ?, ?, ?, ?,    ?,  ?, ? )
### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'xu****@foxmail.com' for key 'user.邮箱'
; Duplicate entry 'xu*****@foxmail.com' for key 'user.邮箱'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'xu*******@foxmail.com' for key 'user.邮箱'

按理说越精确优先级越高,SQLIntegrityConstraintViolationException继承SQLException,为什么返回的却是被Exception异常捕获?求解答

我尝试注释Exception异常捕获,此时异常能被SQLException捕获

阅读 1.2k
avatarAI BotBETA

在Spring Boot中,全局异常处理器的处理顺序主要基于几个因素,但最关键的是方法上的@ExceptionHandler注解指定的异常类型以及这些异常类型之间的继承关系。当你定义了多个@ExceptionHandler方法用于捕获不同类型的异常时,Spring会根据异常的具体类型以及你定义的捕获顺序(实际上是方法的定义顺序,但更准确地说是类型匹配度)来决定调用哪个处理器。

在你的例子中,虽然SQLIntegrityConstraintViolationException继承自SQLException,但Spring在决定调用哪个@ExceptionHandler方法时,并不是简单地按照继承关系从基类到子类进行匹配,而是首先查找与异常类型最精确匹配的方法。然而,由于Java的类型擦除和Spring的异常解析机制,有时候可能会出现一些不太直观的行为。

然而,在大多数情况下,如果SQLIntegrityConstraintViolationException被抛出,并且你有一个专门捕获SQLException的方法,Spring会优先调用这个更具体的方法(即捕获SQLException的方法),因为它更精确地匹配了异常的类型。但在你的情况下,如果SQLIntegrityConstraintViolationException被捕获为Exception而不是SQLException,这通常意味着有其他因素在起作用。

这里有几个可能的原因:

  1. 异常包装SQLIntegrityConstraintViolationException可能被另一个异常包装(比如RuntimeExceptionException),这取决于你的应用程序和Spring Boot的配置。如果它被包装了,那么它可能不再直接匹配SQLException处理器。
  2. 方法签名冲突:虽然这在你的代码中不明显,但理论上如果Spring在解析@ExceptionHandler方法时遇到某种签名冲突(尽管这在这个案例中不太可能),它可能无法正确选择方法。
  3. 配置或版本问题:有时候,Spring Boot的不同版本或特定的配置设置可能会影响异常处理的行为。
  4. 其他@ExceptionHandler干扰:虽然你的示例中没有显示,但如果你的应用中有其他@ExceptionHandler定义在其他类或同一个类中,它们也可能影响异常处理的顺序。
  5. 类加载器问题:在复杂的部署环境中,类加载器问题可能导致Spring无法正确识别异常类型。
  6. 日志中的误导信息:有时候,日志中的异常堆栈跟踪可能包含多个异常,但不一定反映实际被捕获并处理的异常。确保检查的是被实际传递给@ExceptionHandler方法的异常类型。

在你的情况下,当你注释掉捕获Exception的方法时,SQLException的方法能够正确捕获SQLIntegrityConstraintViolationException,这表明SQLIntegrityConstraintViolationException本身没有被包装成另一个类型的异常,并且没有其他@ExceptionHandler干扰。这进一步支持了Spring通常会根据异常类型的精确匹配来选择处理器的观点。

因此,如果你的应用程序在没有注释掉捕获Exception的方法时不能按预期工作,建议检查以下几点:

  • 确保没有其他@ExceptionHandler方法干扰。
  • 检查SQLIntegrityConstraintViolationException是否确实没有被包装成另一个类型的异常。
  • 验证你的Spring Boot版本和配置是否可能导致此行为。
  • 在调试时,仔细检查传递给@ExceptionHandler方法的异常对象的确切类型。
1 个回答

你的代码就是错误的,SQLException及其子类SQLIntegrityConstraintViolationException是个检查时异常,你在运行时捕获,是捕获不到的,这个函数永远不会执行的

/**
 * @param ex 数据库异常
 * @return 后端统一返回结果
 */
@ExceptionHandler
public Result exceptionHandler(SQLException ex){
    String message = ex.getMessage();
    log.error("数据库异常:{}",message);
    if (message.contains("Duplicate entry")){
        String username = message.split(" ")[2];
        return Result.error(username + MessageConstant.ALREADY_EXISTS);
    }else {
        return Result.error(MessageConstant.SQL_UNKNOWN_ERROR);
    }
}

你在日志中看到SQLIntegrityConstraintViolationException错误,是MyBatis输出时,从MyBatisSystemException.getCause()得到的,实际的异常在返回到控制层时,已经经过了spring-jdbcMyBatis的包装处理,你可以改成这样看看具体的异常类型

@ExceptionHandler
public Result exceptionHandler(Exception ex){
    log.error("服务器{}异常:{}",ex.getClass(),ex.getMessage());
    return Result.error("服务器异常");

}
推荐问题
宣传栏