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

今天在写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变量吗?

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

  1. SpringBoot - 参数校验、统一异常、统一响应

    转载自: https://blog.csdn.net/chaitoudaren/article/details/105610962 前言 本篇主要要介绍的就是controller层的处理,一个完整的后 ...

  2. springBoot(5)---单元测试,全局异常

    单元测试,全局异常 一.单元测试 1.基础版 1.引入相关依赖 <!--springboot程序测试依赖,如果是自动创建项目默认添加--> <dependency> <g ...

  3. Springboot的日志管理&Springboot整合Junit测试&Springboot中AOP的使用

    ==============Springboot的日志管理============= springboot无需引入日志的包,springboot默认已经依赖了slf4j.logback.log4j等日 ...

  4. SpringBoot切面Aop的demo简单讲解

    前言 本篇文章主要介绍的是SpringBoot切面Aop的demo简单讲解. SpringBoot Aop 说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码. 切面(Aop) 一.概 ...

  5. Spring全家桶——SpringBoot之AOP详解

    Spring全家桶--SpringBoot之AOP详解 面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方 ...

  6. 选择目录,选择文件夹的COM组件问题。在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常。

    异常: 在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式.请确保您的 Main 函数带有 STAThreadAttribute 标记. 只有将调试器附加到该进程才会引发此异常. ...

  7. 将不确定变为确定~DateTime.MinValue和MaxValue引发的异常

    回到目录 问题描述: SqlDateTime 溢出.必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间 概念相关 .Net中的DateTime结 ...

  8. WPF控件ComboBox 每个Item的ToolTip引发的异常

    介绍 首先介绍下要实现的任务.做一个下拉框,当选择每个项的时候将鼠标发在上面显示该项的ToolTip的内容(Image). 实现 Model: public class SkinInfo : Noti ...

  9. VS2010 F5调试时出现:“ 尝试运行项目时出错:未捕获通过反射调用的方法引发的异常”解决

    VS2010 F5调试时出现 尝试运行项目时出错:未捕获通过反射调用的方法引发的异常 两个解决方法:1) 打开项目属性,选择调试选项卡,将“启用非托管代码调试”一项钩上.2) 打开项目属性,选择调试选 ...

  10. WPF关于“在“System.Windows.Markup.StaticResourceHolder”上提供值时引发了异常。”问题解决办法

    在WPF中添加样式,在MainWindow.xaml使用自定义按钮FButton时报错,报错信息如下: "System.Windows.Markup.XamlParseException&q ...

随机推荐

  1. Java SpringBoot FTP 上传下载文件

    POM 添加依赖 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all< ...

  2. SpringBoot 自定义初始化任务 Runner

    在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等.可以通过实现Runner接口完成以上工作. 两者只是参数上的区别 方式一 实现 CommandLineRunner 接口 ...

  3. 【django-vue】课程表数据录入 课程分类接口 所有课程接口 课程详情接口 所有章节接口 课程列表前端 课程详情前端

    目录 上节回顾 APSchudler 双写一致性 今日内容 1 课程表数据录入 2 课程分类接口 2.1 路由 2.2 序列化类 2.3 视图类 3 所有课程接口(过滤,排序) 3.1 表模型 3.2 ...

  4. C# golang 开10000个无限循环的性能

    知乎上有人提了个问题,可惜作者已把账号注销了. 复制一下他的问题,仅讨论技术用,侵删. 问题 作者:知乎用户fLP2gX 链接:https://www.zhihu.com/question/63484 ...

  5. 【Boost】Windows 下个人在配置 Boost 踩到的坑以及解决方案

    要编译的 Boost 版本:1.82.0 假设 Boost 的根目录为 ${boost_root}$ 如果不想编译可以使用博主编译后的Boost:https://pan.baidu.com/s/1s2 ...

  6. CodeForces-339B-Xenia and Ringroad (循环队列,水题)

    Xenia lives in a city that has n houses built along the main ringroad. The ringroad houses are numbe ...

  7. 【每日一题】29.maze (BFS 进阶)

    补题链接:Here 本题代码由贺佬完成 这道题基本算一道 BFS 进阶题,有少许细节要小心 (WA1发.. #include <bits/stdc++.h> #define x first ...

  8. vivo 悟空活动中台 - 微组件状态管理(下)

    本文首发于 vivo互联网技术 微信公众号 链接: https://mp.weixin.qq.com/s/1DzTYIExVbK0uE_Oc7IHYw作者:悟空中台研发团队 [悟空活动中台]系列往期精 ...

  9. 2019 篇 - 分享数百个 HT的工业互联网 2D 3D 可视化应用案例

    继<分享数百个 HT 工业互联网 2D 3D 可视化应用案例>2018 篇,图扑软件定义 2018 为国内工业互联网可视化的元年后,2019 年里我们与各行业客户进行了更深度合作,拓展了H ...

  10. 云原生 Serverless Database 使用体验

    作者 | 李欣 近十年来互联网技术得到了飞速的发展,越来越多的行业加入到了互联网的矩阵,由此带来了更为丰富且复杂的业务场景需求,这对于数据应用系统的性能无疑是巨大的挑战.​ 关系型数据库 MySQL ...