首页 > 其他分享 >【SpringBoot】当AOP引发的异常与@RestControllerAdvice擦肩而过:异常处理的盲点揭秘

【SpringBoot】当AOP引发的异常与@RestControllerAdvice擦肩而过:异常处理的盲点揭秘

时间:2024-01-29 15:48:28浏览次数:28  
标签:Exception SpringBoot 处理 ExceptionHandler PException AOP 异常 class

各位上午/下午/晚上好呀!

今天在写bug的时候发现一个这样的问题:

AOP抛出的异常竟然没有被@RestControllerAdvice注解修饰的异常统一处理类处理。

 

需求是这样子滴:对某些加了自定义注解的方法进行切面处理,通过条件判断是否有权限执行该方法。

伪代码大概长这个样子:

    @Around("pointcut()")
    public Object aroundScheduledMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!isAccess()) {
            throw new PException();
        }
        joinPoint.proceed();
    }         

其中,PException就是个Exception的子类,没什么特别的:

public class PException extends Exception {
}

在@RestControllerAdvice的统一异常处理类中处理了这个异常:

    @ExceptionHandler({PException.class})
    public Object handleAuthFailedException(PException e, HttpServletResponse response) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        return "Permission denied";
    }

到这里应该一切正常吧?讲道理@ExceptionHandler({PException.class})会处理AOP抛出的PException,并返回401

可是啪一跑,就不出意外的出意外了。

执行发现当权限不足时,也就是满足if条件时,主动抛出的异常并没有被@ExceptionHandler({PException.class})方法处理,而是返回了Internal Server Error。

Internal Server Error是哪里来的?别急,@RestControllerAdvice注解修饰的统一异常处理类中确实有这个,是用来捕获Exception类的,就是给整个Controller的异常处理兜底。代码如下:

    @ExceptionHandler({Exception.class})
    public Object handleException(Exception e, HttpServletResponse response) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        log.error("Internal Server Error", e);
        return "Internal Server Error";
    }

虽然我觉得上面这个东西的出现并不合理:

这个异常兜底可能会导致部分由Spring框架本身处理的异常被屏蔽掉,比如当接口返回的HttpRequestMethodNotSupportedException异常,实际上的response code是由Spring来设置成405的。如果在@RestControllerAdvice中没有处理HttpRequestMethodNotSupportedException,就会由兜底返回Internal Server Error。

不过有的项目确实希望把所有的异常控制在一定的范围内,不对外暴露,比如像下面这样:

    @ExceptionHandler({Exception.class})
    public Object handleException(Exception e, HttpServletResponse response) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        log.error("Internal Server Error", e);
        return "Bad Request";
    }

日志中会完整的输出Exception信息,但是对外只会报错400 Bad Request来隐藏服务的异常。

扯远了,我们在这里不讨论兜底处理Exception的合理性,只讨论PException到底哪里去了,为什么没有被@ExceptionHandler({PException.class})方法处理,反而被@ExceptionHandler({Exception.class})方法处理了

问题分析:

 

首先看日志:

发现我们定义的PException信息确实打印出来了,不过抛出了一个UndeclaredThrowableException,嗯?这样异常哪里来的呢?跟我们定义的PException之间又是什么关系?

Debug断点看看到底什么时候处理了PException这个异常:

 继续

然后就会走到这么一个地方,好像看到日志中那个UndeclaredThrowableException异常了,继续

 哎,确实走到了throw new UndeclaredThrowableException这行代码,而且我们本身的PException被作为一个参数传进去了。

 追踪这个方法,发现传入的PException被赋值给了一个undeclaredThrowable变量,这个变量有什么用,我们一会儿再说,先记下它

 也就是说,在AOP中抛出的异常最终被包装成了一个UndeclaredThrowableException,但是有一个前提:

通过反射判断是否属于切点执行方法的声明的Exception,如果不是且不属于RuntimeException,那么将会使用UndeclaredThrowableException包装原始的Exception。

判断方法如下:

抛出UndeclaredThrowableException后由@ExceptionHandler({Exception.class})方法捕获处理。

问题已经定位到了,解决方案也显而易见:

1.让PException继承RuntimeException,不过在某些情况下未必合理。根据业务来。

2.在切点执行方法中声明抛出PException,即使在该方法中根本不会抛出该异常,但只要在AOP中可以抛出,那就需要声明。

3.去掉兜底的@ExceptionHandler({Exception.class}),让SpringBoot处理。

 

到这,问题已经解决了。吗?

你难道一点儿都不好奇?

SpringBoot为什么可以正常的处理PException?

 

具体调试流程就不展开了,最终在SpringBoot的ExceptionHandlerExceptionResolver中找到了答案,看下面的422和423行代码,cause调用了Throwable的getCause()方法:

看下这个方法在UndeclaredThrowableException中的实现:

 到这是不是都说的通了呀?还记得之前提到的undeclaredThrowable变量吗?

 

标签:Exception,SpringBoot,处理,ExceptionHandler,PException,AOP,异常,class
From: https://www.cnblogs.com/maerpao/p/17975000

相关文章

  • .Net Framework:MinIO objectname异常
     minio-dotnetgithub地址:github.com/minio/minio-dotnet1.异常现象:在调用PutObjectAsync/FileExist/FGetObject等方法操作MinIO时,objectname同时包含汉字、英文括号,MinIO内部throw异常:AuthorizationException:Therequestsignaturewecalculateddoesnotmatchthes......
  • SpringBoot统一结果返回,统一异常处理,大牛都这么玩
    引言在开发SpringBoot应用时,我们经常面临着不同的控制器方法需要处理各种不同类型的响应结果,以及在代码中分散处理异常可能导致项目难以维护的问题。你是否曾经遇到过在不同地方编写相似的返回格式,或者在处理异常时感到有些混乱?这些看似小问题的积累,实际上可能对项目产生深远的......
  • 新来的一个同事,把SpringBoot参数校验玩的那叫一个优雅
    介绍在开发现代应用程序时,数据验证是确保用户输入的正确性和应用程序数据完整性的关键方面。SpringBoot提供了强大的数据验证机制,使开发者能够轻松地执行验证操作。本文将深入介绍SpringBoot中的Validation,以及如何在应用程序中正确使用它。为什么使用数据验证?1.用户输......
  • 异常检测、自动告警,业务问题分钟级识别
    跨国业务覆盖范围广、用户多、业务量大,运维面临巨大挑战?应用平台AppStage运维中心来助力!本文分享自华为云社区《异常检测、自动告警,业务问题分钟级识别》,作者:开天aPaaS小助手。跨国业务覆盖范围广、用户多、业务量大,运维面临巨大挑战?应用平台AppStage运维中心来助力!异常检测......
  • SpringBoot + SpEL,轻松搞定复杂权限控制
    对于在Springboot中,利用自定义注解+切面来实现接口权限的控制这个大家应该都很熟悉,也有大量的博客来介绍整个的实现过程,整体来说思路如下:自定义一个权限校验的注解,包含参数value配置在对应的接口上定义一个切面类,指定切点在切入的方法体里写上权限判断的逻辑乍一看,没毛病,学......
  • springboot中@Repository 和 @Mapper的区别
    在springboot中他们两都是数据访问层的注解(在定义方面)@Repository:@Repository注解通常用于对DAO(DataAccessObject)组件进行标识。它告诉Spring框架,被注解的类是用于数据访问的组件,可以通过Spring的组件扫描机制自动注册为SpringBean,并且可以将底层的数据访问异......
  • SpringBoot日志配置
    1.简介Spring使用spring5及以后commons-logging被spring直接自己写了。支持log4j2,logback是默认使用的。虽然日志框架很多,但是我们不用担心,使用SpringBoot的默认配置就能工作的很好。 SpringBoot怎么把日志默认配置好的1、每个starter场景,都会导入一个核心场景......
  • 如何改Maven Dependencies的源码,如何把springboot组件的源码改造后使用
    由于springboot提供的源码有些地方不太符合预期,所以需要改动改动,这里就会说到,如何改MavenDependencies的源码。如何把springboot组件的源码改造后使用。v修改源码的几种方式直接在自己工程中建同包同类名的类进行替换采用@Primary注解排除需要替换的jar包中的类@Bean......
  • 生产环境出现 bug 自动生成异常追踪-SRE与开发自动化协同
    作者:观测云数据智能部产品方案架构师范莹莹简介生产环境bug的定义:RUM应用和APM应用的 error_stack 信息被捕捉后成为bug。以APM新增错误巡检为例,当出现新错误时,在观测云控制台的「事件」模块下生成新的事件报告,捕捉为bug。同时利用 DatafluxFunc 创建异常追踪,......
  • Springboot CRUD简单实现
    SpringBoot对Spring的改善和优化,它基于约定优于配置的思想,提供了大量的默认配置和实现使用SpringBoot之后,程序员只需按照它规定的方式去进行程序代码的开发即可,而无需再去编写一堆复杂的配置SpringBoot的主要功能如下:起步依赖:SpringBoot以功能化的方式将需要的依赖进行组装,并......