SpringBoot统一异常处理

统一异常捕获

使用注解的方式统一异常处理时,可以使用@RestControllerAdvice配合@ExceptionHandler拦截控制器的异常。(@RestControllerAdvice = @ControllerAdvice + @ResponseBody)

@RestControllerAdvice注解是Spring框架中用于定义全局异常处理类的注解,它结合了@ControllerAdvice和@ResponseBody的功能,用于RESTful风格的应用程序。

当SpringBoot应用启动时,Spring容器会自动扫描并加载带有@RestControllerAdvice注解的类,将其实例化并加载到容器中进行管理。一旦控制层在处理请求时抛出异常,Spring MVC的异常处理机制就会被出发,并且@RestControllerAdvice中定义的异常处理方法将被调用。

在标注了的@RestControllerAdvice类中,我们可以定义多个@ExceptionHandler方法,这些方法会根据其参数类型与抛出的异常类型进行匹配,实现对特定异常的捕获和处理。

使用@RestControllerAdvice可以避免在每个Controller中重复编写异常处理代码,实现代码的复用和集中管理。异常处理方法可以返回ResponseEntity对象,通过这种方法可以自定义响应的状态码和响应体。

常见可被捕获的异常类型
  1. Exception:普通的Java异常,是大多数其他异常的基类。
  2. RuntimeException:运行时异常,包括NullPointerException、IllegalArgumentException等。
  3. HTTP状态码异常:例如,HttpStatus.NOT_FOUND表示资源未找到,HttpStatus.BAD_REQUEST表示请求错误等。
  4. ValidationException:数据验证异常,例如使用JSR-303或Hibernate Validator进行的数据验证失败。
  5. MethodArgumentNotValidException:请求方法参数验证失败时抛出的异常。
  6. HttpMessageNotReadableException:当请求的HTTP消息无法读取或解析时抛出的异常,例如请求体格式错误。
  7. HttpRequestMethodNotSupportedException:当请求的HTTP方法不受支持时抛出的异常。
  8. MissingServletRequestParameterException:当请求缺少必需的参数时抛出的异常。
  9. BindException:数据绑定失败时抛出的异常,通常与表单提交相关。
  10. ConstraintViolationException:当使用Bean验证(如Hibernate Validator)时,违反约束条件时抛出的异常。
  11. NoHandlerFoundException:当找不到适合处理当前请求的处理程序时抛出的异常。
  12. 自定义的异常类。
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 全局异常捕捉处理
     */
    @ExceptionHandler(value = Exception.class)
    public ResultEntity<Void> errorHandler(Exception ex) {
        log.error("errorHandler----> 全局异常 ex = ", ex);
        return ResultEntity.createResponseEntity(ResultEntity.CODE_ERROR, ex.getMessage());
    }

    /**
     * 拦截捕捉自定义异常 GlobalException.class
     */
    @ExceptionHandler(value = GlobalException.class)
    public ResultEntity<Void> globalExceptionHandler(GlobalException ex) {
        log.error("myErrorHandler----> 全局异常 ex = ", ex);
        return ResultEntity.createResponseEntity(ex.getCode(), ex.getMsg());
    }

    /**
     * 拦截参数校验异常 MethodArgumentNotValidException.class
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResultEntity<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));
        log.error("ArgumentNotValidException ----> 接口请求参数异常 ex = {}",  message);
        return ResultEntity.paramError(message);
    }
    
}

如何精准捕获SQLException

统一异常处理时,如果有SQL执行的错误,会被Exception.class捕获,此时如果直接返回ex.getMessage()的话,会提示一长串错误信息,更严重的是会暴露数据库表结构,增加数据库的漏洞风险。直接把ex.getMessage()替换为“XXX异常”又比较笼统,所以最好是能够处理SQL操作抛出的异常。
通过查看日志发现,我当前抛出的异常的是DuplicateKeyException

org.springframework.dao.DuplicateKeyException: 
### Error updating database.  Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '0-MY_CUSTOMER-0' for key 'tb_process_model.uk_corporation_num_model_key_enable_status'
### The error may exist in com/huayu/processflow/dao/mapper/ProcessModelMapper.java (best guess)
### The error may involve com.huayu.processflow.dao.mapper.ProcessModelMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO tb_process_model  ( corporation_num,  model_name, model_content,  model_key, remark, enable_status, use_scope, total_point, recheck_type, updater_num, updater_name, creator_num, creator_name, create_time, update_time )  VALUES (  ?,  ?, ?,  ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?  )
### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '0-MY_CUSTOMER-0' for key 'tb_process_model.uk_corporation_num_model_key_enable_status'
; Duplicate entry '0-MY_CUSTOMER-0' for key 'tb_process_model.uk_corporation_num_model_key_enable_status'

这个异常是由Spring框架内部定义的,继承自Exception,所以是被Exception捕获,继承图如下:
image.png
如果想要捕获此异常,最匹配的还是使用DuplicateKeyException.class。加入以下代码,可以正常捕获唯一索引冲突异常。

    @ExceptionHandler(value = DuplicateKeyException.class)
    public ResultEntity<Void> duplicateKeyExceptionHandler(DuplicateKeyException ex) {
        log.error("duplicateKeyExceptionHandler: {}", ex.toString());
        return ResultEntity.createResponseEntity(ResultEntity.CODE_ERROR, "数据库操作出现错误");
    }

但这个异常它的底层还是JDBC的SQLIntegrityConstraintViolationException,就想知道它是怎么转换为DuplicateKeyException的。
通过查看源码发现,可以通过org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator来将SQLException转换为自身定义的异常,方便外部捕获使用。sqlErrorCodes,即同一包下的SQLErrorCodes
image.png
在SQLErrorCodes中,定义了一系列的特定异常错误码。
image.png
在同一包下,存在一个配置文件,定义了Spring配置的JDBC异常码,涵盖好几种数据库,其中MySQL的定义如下:
image.png
所以,我们可以知道MySQL唯一索引或主键冲突时,返回的就是1062的错误码,并通过Spring中对SQL异常进行转换得到DuplicateKeyException类型的异常。但是这个异常的内部cause还是属于SQL异常。

RestControllerAdvice的拦截顺序

除了上述的方法,还可以通过RestControllerAdvice的拦截顺序来精准捕获SQLException。在出现多个处理器时,RestControllerAdvice的拦截顺序会有下面两种情况。

1、存在一个类中

调试源码可见匹配顺序为:异常层级高的优先调用,即子类异常处理器优先。

2、存在不同的类中


源码可见:

通过以上代码可以看到与多个异常处理类放入LinkedHashMap的顺序有关。继续看源码:

此处对异常类进行了排序:

此处看到可以利用Order指定顺序,如果没有,则默认最小顺序;
那么,如果都没有指定顺序的话,那就是list中的顺序,源码:

获取所有的beanDefinitionNames,再遍历寻找标注了异常处理注解的类,放入list中(存在父容器的合并后再遍历寻找)。
所以,通过加一个新的处理器,并赋予更高的Order级别,也可以捕获SQLException。

@Order(value = Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice
@Slf4j
public class SQLExceptionHandler {

    @ExceptionHandler(value = SQLException.class)
    public ResultEntity<Void> sqlErrorHandler(SQLException ex) {
        log.error("sqlErrorHandler: {}", ex.toString());
        return ResultEntity.createResponseEntity(ResultEntity.CODE_ERROR, "数据库操作出现错误");
    }

}

当多个 @RestControllerAdvice 类存在时,它们的拦截顺序可以通过几种方式来确定:

  1. @Order 注解:可以使用 @Order 注解来指定类的加载顺序,值越小优先级越高 。
  2. @Priority 注解:与 @Order 类似,@Priority 注解来自 JSR 250 标准,也用于指定优先级,且优先级比 @Order 更高 。
  3. @Primary 注解:当存在多个候选 Bean 时,标记为 @Primary 的 Bean 将被优先加载 。
  4. Ordered 接口:实现 Ordered 接口的类可以通过实现 getOrder() 方法来指定顺序,值越小优先级越高 。
  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目详细功能参考项目演示内容即可项目优势:1、项目从零开始到完结 附带视频,源码以及相关辅助资料,适合学习使用,项目也可拿来即用。2、几乎全手写代码,功能流程详细 跟着可以独立完成,附带详细代码相关常见bug 和 调试解决方案,让大家学会跟踪快速解决问题。3、系统后端使用LayUI技术,对页面不精通的小伙伴也可以快速完成精美页面的设计及应用,支持统一后台管理,也可拿来做其他项目通用后台4、针对layui 相关技术点薄弱的学员提供相关技术点学习,让快速上手完成项目研发5、选材来自生活,项目真实感强,可用学习使用和就业面试使用,适合作为面试中提高实际项目经验...6、该项目前后端分离,满足前沿技术点..项目技术栈:- 数据库:MySQL8.0- 后端技术:SpringBoot,MyBatisPlus,JWT 等- 日志技术:Log4j- 数据库连接池:druid- 前端技术:LayUI, axios,Echarts,Ztree 等- Web容器:Apache Tomcat 9- 项目管理工具:Maven3.6- 思维导图设计工具:XMIND 8- 开发工具: IDEA2020, WebStorm2020- 数据库设计软件:Power Designer16.5特别提示:1、涉及相关技术点学习,更多侧重大学生或无项目经验以及项目经验较少的学员入门到项目完结项目实战2、项目中功能处理大多提供多种解决方式,如跨域访问,更多让大家了解解决方式的同时学会技术点应用3、加入bug的调试以及代码跟踪处理,更好的让学员更多学会如何解决问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值