源码剖析Springboot自定义异常

博主看到新服务是封装的自定义异常,准备入手剖析一下,自定义的异常是如何进行抓住我们请求的方法的异常,并进行封装返回到。废话不多说,先看看如何才能实现封装异常,先来一个示例:

 1 @ControllerAdvice
 2 public class TstExceptionHandle{
 3 
 4     @ExceptionHandler(Exception.class)
 5     public void myExceptionHandle(HttpServletResponse response){
 6         response.setStatus(403);
 7         System.out.println("做封装处理");
 8     }
 9 
10 }

  博主只做了简单的配置示例,主要的是进行源码剖析Springboot是如何获取自定义异常并进行返回的。来吧!

  第一步:肯定是在Springboot启动的过程中进行的异常处理初始化,于是就找到了handlerExceptionResolver类,在创建该类的时候,会进行添加我们自定义异常。

 1     public HandlerExceptionResolver handlerExceptionResolver(
 2             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
 3         List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
 4         //不用管这个方法,这个方法主要进行的是调用实现了WebMvcConfigurer接口bean的configureHandlerExceptionResolvers方法,系统的都是空方法
 5         configureHandlerExceptionResolvers(exceptionResolvers);
 6         if (exceptionResolvers.isEmpty()) {
 7             //我们的在这里才添加,我们看看这个方法
 8             addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
 9         }
10         extendHandlerExceptionResolvers(exceptionResolvers);
11         HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
12         composite.setOrder(0);
13         composite.setExceptionResolvers(exceptionResolvers);
14         return composite;
15     }

 1     protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
 2             ContentNegotiationManager mvcContentNegotiationManager) {
 3 
 4         ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
 5         exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
 6         exceptionHandlerResolver.setMessageConverters(getMessageConverters());
 7         exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
 8         exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
 9         if (jackson2Present) {
10             exceptionHandlerResolver.setResponseBodyAdvice(
11                     Collections.singletonList(new JsonViewResponseBodyAdvice()));
12         }
13         if (this.applicationContext != null) {
14             exceptionHandlerResolver.setApplicationContext(this.applicationContext);
15         }
16         //上面的 都是设置的属性,跟我们没啥大关系,主要在这里进行的添加自定义异常处理
17         exceptionHandlerResolver.afterPropertiesSet();
18         exceptionResolvers.add(exceptionHandlerResolver);
19 
20         ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
21         responseStatusResolver.setMessageSource(this.applicationContext);
22         exceptionResolvers.add(responseStatusResolver);
23 
24         exceptionResolvers.add(new DefaultHandlerExceptionResolver());
25     }

    最主要的初始化过程在这里,从这些代码中就可以看到为什么我们自定义异常需要进行使用@ControllerAdvice,并且方法使用@ExceptionHandler(Exception.class)注解了

 1     @Override
 2     public void afterPropertiesSet() {
 3         // Do this first, it may add ResponseBodyAdvice beans
 4         //走这里初始化,添加
 5         initExceptionHandlerAdviceCache();
 6 
 7         if (this.argumentResolvers == null) {
 8             List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
 9             this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
10         }
11         if (this.returnValueHandlers == null) {
12             List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
13             this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
14         }
15     }
16 
17 
18     org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
19     private void initExceptionHandlerAdviceCache() {
20         if (getApplicationContext() == null) {
21             return;
22         }
23         //看到这里基本就知道啥意思了,找出带有@ControllerAdvice的注解bean
24         List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
25         for (ControllerAdviceBean adviceBean : adviceBeans) {
26             Class<?> beanType = adviceBean.getBeanType();
27             if (beanType == null) {
28                 throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
29             }
30             //找出当前bean的异常处理方法
31             ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
32             if (resolver.hasExceptionMappings()) {
33                 this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
34             }
35             if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
36                 this.responseBodyAdvice.add(adviceBean);
37             }
38         }
39 
40         if (logger.isDebugEnabled()) {
41             int handlerSize = this.exceptionHandlerAdviceCache.size();
42             int adviceSize = this.responseBodyAdvice.size();
43             if (handlerSize == 0 && adviceSize == 0) {
44                 logger.debug("ControllerAdvice beans: none");
45             }
46             else {
47                 logger.debug("ControllerAdvice beans: " +
48                         handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
49             }
50         }
51     }

  找到类后,是如何找到方法的呢?主要看如何创建ExceptionHandlerMethodResolver的过程。

 1 public ExceptionHandlerMethodResolver(Class<?> handlerType) {
 2     //EXCEPTION_HANDLER_METHODS的定义:
 3     //public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
 4     //            AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
 5     //所以他会寻找带有ExceptionHandler注解的方法
 6         for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
 7             //寻找方法注解上配置的捕获的异常类,并添加,如果有两个方法都对一个异常进行自定义处理了,怎么办呢。
 8             for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
 9                 //他会出异常的。不过前提是同一个类里,不同类对同一个异常进行自定义的话,谁在前面就有谁来处理
10                 addExceptionMapping(exceptionType, method);
11             }
12         }
13     }

  添加自定义异常的时候抛异常是在这里

1     private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
2         Method oldMethod = this.mappedMethods.put(exceptionType, method);
3         //在这里,已经显示出来了,博主就不试了
4         if (oldMethod != null && !oldMethod.equals(method)) {
5             throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
6                     exceptionType + "]: {" + oldMethod + ", " + method + "}");
7         }
8     }

  好了。所有异常添加完毕了,我们来测试一下异常来的时候,Springboot是如何选择自定义异常并返回的,我们上面所有的操作都是在创建HandlerExceptionResolver时进行的,为什么要添加到HandlerExceptionResolver这里呢?看一下代码:

 1 //第一次请求进来时,会先查找是否有自定义异常,如果有的话添加,没有记录日志就完了
 2     private void initHandlerExceptionResolvers(ApplicationContext context) {
 3         this.handlerExceptionResolvers = null;
 4 
 5         if (this.detectAllHandlerExceptionResolvers) {
 6             // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
 7                         //这里会在beanfactroy中查找到HandlerExceptionResolver类,刚才初始化的时候,我们所有的自定义异常都在里面 
 8             Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
 9                     .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
10             if (!matchingBeans.isEmpty()) {
11                 this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
12                 // We keep HandlerExceptionResolvers in sorted order.
13                 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
14             }
15         }
16         else {
17             try {
18                 HandlerExceptionResolver her =
19                         context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
20                 this.handlerExceptionResolvers = Collections.singletonList(her);
21             }
22             catch (NoSuchBeanDefinitionException ex) {
23                 // Ignore, no HandlerExceptionResolver is fine too.
24             }
25         }
26 
27         // Ensure we have at least some HandlerExceptionResolvers, by registering
28         // default HandlerExceptionResolvers if no other resolvers are found.
29         if (this.handlerExceptionResolvers == null) {
30             this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
31             if (logger.isTraceEnabled()) {
32                 logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
33                         "': using default strategies from DispatcherServlet.properties");
34             }
35         }
36     }            

  走完初始化,经过过滤器,拦截器终于到了我们的请求方法,我们的方法还报错了,所以会走到异常中,我们DispatcherServlet会进行抓住异常,然后回调用我们的processDispatchResult方法,大家可以自己看一下org/springframework/web/servlet/DispatcherServlet.java的源码,然后我们来分析一下这个方法都干啥了吧

 1     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
 2             @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
 3             @Nullable Exception exception) throws Exception {
 4 
 5         boolean errorView = false;
 6 
 7         if (exception != null) {
 8             if (exception instanceof ModelAndViewDefiningException) {
 9                 logger.debug("ModelAndViewDefiningException encountered", exception);
10                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
11             }
12             else {
13                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
14                 //如果请求方法有异常,则进行处理,并返回ModelAndView
15                 mv = processHandlerException(request, response, handler, exception);
16                 errorView = (mv != null);
17             }
18         }
19     .........
20     }

  那Springboot是如何选择哪一个是符合条件的自定义异常处理呢?如果我们定义了两个处理类,都对同一个异常进行捕获并返回不一样的信息咋办呢?看源码吧

 1 //这里会选择符合条件的自定义异常
 2     protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
 3             @Nullable HandlerMethod handlerMethod, Exception exception) {
 4 
 5         Class<?> handlerType = null;
 6 
 7         if (handlerMethod != null) {
 8             // Local exception handler methods on the controller class itself.
 9             // To be invoked through the proxy, even in case of an interface-based proxy.
10             handlerType = handlerMethod.getBeanType();
11             ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
12             if (resolver == null) {
13                 resolver = new ExceptionHandlerMethodResolver(handlerType);
14                 this.exceptionHandlerCache.put(handlerType, resolver);
15             }
16             Method method = resolver.resolveMethod(exception);
17             if (method != null) {
18                 return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
19             }
20             // For advice applicability check below (involving base packages, assignable types
21             // and annotation presence), use target class instead of interface-based proxy.
22             if (Proxy.isProxyClass(handlerType)) {
23                 handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
24             }
25         }
26         //exceptionHandlerAdviceCache这个map是我们添加 的自定义异常
27         for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
28             ControllerAdviceBean advice = entry.getKey();
29             //这个判断条件是查看是否有符合条件的自定义异常,如果有两个的话,
30             if (advice.isApplicableToBeanType(handlerType)) {
31                 ExceptionHandlerMethodResolver resolver = entry.getValue();
32                 Method method = resolver.resolveMethod(exception);
33                 if (method != null) {
34                     return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
35                 }
36             }
37         }
38 
39         return null;
40     }

  逻辑基本是上面的,但是真正处理是否符合是在这里的一个方法中:

 1 public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
 2         return this.beanTypePredicate.test(beanType);
 3     }
 4     public boolean test(Class<?> controllerType) {
 5          ///默认不配的其他属性的时候是返回true的,就是对所有包下的异常都适用
 6         if (!hasSelectors()) {
 7             return true;
 8         }
 9         else if (controllerType != null) {
10             //我们的@ControllerAdvice注解是有basePackages属性的,只有匹配成功才会返回,否则就算自定义异常想要捕获,不在捕获包范围下不管该异常
11             for (String basePackage : this.basePackages) {
12                 if (controllerType.getName().startsWith(basePackage)) {
13                     return true;
14                 }
15             }
16             for (Class<?> clazz : this.assignableTypes) {
17                 if (ClassUtils.isAssignable(clazz, controllerType)) {
18                     return true;
19                 }
20             }
21             for (Class<? extends Annotation> annotationClass : this.annotations) {
22                 if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
23                     return true;
24                 }
25             }
26         }
27         return false;
28     }

  到这里基本如何写自定义异常、以及为什么这么写、底层做了哪些判断都已经讲解完了,自定义异常在工作中还是非常常用的一种手段,因为我们不可能暴露出我们内部的错误信息直接返回给用户,不仅用户体验不好,并且安全性也极其差。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/746536.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

按位与、或、异或操作符

目录 & --- 按位与操作符 按位与操作符运用规则 按位与操作符相关代码 按位与操作符相关代码验证 | --- 按位或操作符 按位或操作符运用规则 按位或操作符相关代码 按位或操作符相关代码验证 ^ --- 按位异或操作符 按位异或操作符运用规则 按位异或操作符相关代…

[AI开发配环境]VSCode远程连接ssh服务器

文章目录 总览&#xff1a;ssh连接远程服务器连接免密登录&#xff1a;Docker&#xff1a;ssh连接远程宿主机后&#xff0c;进一步连接并使用其中的docker容器reload window 配置解释器&#xff1a;CtrlP&#xff0c;在上面输入“>python”, 然后选selecet interpreter运行命…

ubuntu如何切换到root用户

1、主要指令&#xff1a; sudo -i su root 2、示例 3、其他说明 在Ubuntu&#xff08;以及大多数其他基于Linux的操作系统中&#xff09;&#xff0c;切换到root用户通常意味着获得了对系统的完全访问权限。这种权限允许执行以下操作&#xff08;但不限于这些&#xff09;…

【C语言】解决C语言报错:Double Free

文章目录 简介什么是Double FreeDouble Free的常见原因如何检测和调试Double Free解决Double Free的最佳实践详细实例解析示例1&#xff1a;重复调用free函数示例2&#xff1a;多次释放全局变量指针示例3&#xff1a;函数间传递和释放指针 进一步阅读和参考资料总结 简介 Doub…

【ajax实战03】拦截器

一&#xff1a;axios拦截器 拦截器分类&#xff1a; 请求拦截器以及响应拦截器 拦截器作用&#xff1a; 在请求或响应被then或catch处理前拦截它们 二&#xff1a;请求拦截器 作用&#xff1a; 发起请求之前&#xff0c;调用一个配置函数&#xff0c;对请求参数进行设置…

MyBatis案例

目录 一、配置文件1.数据与环境准备1.1 创建tb_brand表1.2 在Pojo中创建实体类Brand.java1.3 在test文件夹下的java中创建测试类1.4 安装MyBatisX插件 二、增删改查1. 查询 一、配置文件 1.数据与环境准备 1.1 创建tb_brand表 -- 删除tb_brand表 drop table if exists tb_bra…

什么是大模型?一文读懂大模型的基本概念

大模型是指具有大规模参数和复杂计算结构的机器学习模型。本文从大模型的基本概念出发&#xff0c;对大模型领域容易混淆的相关概念进行区分&#xff0c;并就大模型的发展历程、特点和分类、泛化与微调进行了详细解读&#xff0c;供大家在了解大模型基本知识的过程中起到一定参…

【Qt】初识QtQt Creator

一.简述Qt 1.什么是Qt Qt 是⼀个 跨平台的 C 图形⽤⼾界⾯应⽤程序框架 。它为应⽤程序开发者提供了建⽴艺术级图形界⾯所需的所有功能。它是完全⾯向对象的&#xff0c;很容易扩展。Qt 为开发者提供了⼀种基于组件的开发模式&#xff0c;开发者可以通过简单的拖拽和组合来实现…

Energy-based PINN在固体力学中的运用

简介 物理信息神经网络&#xff08;Physic informed neural network&#xff0c;PINN&#xff09;已经成为在有限差分、有限体积和有限元之后的另一种求解偏微分方程组的范式&#xff0c;受到学者们广泛关注。 在固体力学领域有两类不同的PINN: &#xff08;1&#xff09;PDE…

mac卡牌游戏:堆叠大陆 Stacklands for Mac 中文安装包

Stacklands 是一款轻松益智的堆叠游戏。玩家需要在游戏中不断堆叠不同形状和大小的方块&#xff0c;使它们尽可能地稳定地堆放在一起。游戏中有多种不同的关卡和挑战&#xff0c;玩家需要通过合理的堆叠方式来完成每个关卡。游戏画面简洁明快&#xff0c;操作简单直观&#xff…

跟我从零开始学C++(C++代码基础)5

引言 小伙伴们&#xff0c;在经过一些基础定义和指针&#xff0c;数组&#xff0c;函数的洗礼后&#xff0c;我相信大家肯定都已经对C编程有了新的认知&#xff0c;同时呢&#xff0c;坚持下来的小伙伴们肯定都是好样的&#xff0c;大家都是很棒的&#xff0c;现在我们来学一学…

开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(二)

一、前言 使用 FastAPI 可以帮助我们更简单高效地部署 AI 交互业务。FastAPI 提供了快速构建 API 的能力,开发者可以轻松地定义模型需要的输入和输出格式,并编写好相应的业务逻辑。 FastAPI 的异步高性能架构,可以有效支持大量并发的预测请求,为用户提供流畅的交互体验。此外,F…

Leetcode Hot100之矩阵

1. 矩阵置零 题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 解题思路 题目要求进行原地更改&#xff0c;也就是不能使用额外的空间&#xff0c;因此我们可以使用第一行的元素来记录对应的…

【软件下载】Folx详细安装教程视频-Folx软件最新版下载

根据大数据调查表明Acceleration PRO下载&#xff1a;抽出多达10个流的故障能够显着提高下载速度。根据行业数据显示与iTunes PRO集成&#xff1a;通过将Folx集成到iTunes来下载歌曲和视频&#xff0c;能够在下载后立即自动添加到iTunes库。实际上我们可以这样讲通过代理下载&a…

Git 冲突处理指南:恢复 Git Reset

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…

10个AI高考上岸朋友圈文案设计

高考是人生中的一个重要时刻&#xff0c;上岸后分享朋友圈的文案可以既表达喜悦&#xff0c;也可以展现对未来的期待。以下是10个不同风格的高考上岸朋友圈文案&#xff0c;供你参考&#xff1a; 1. **梦想成真版**&#xff1a; "十年磨一剑&#xff0c;今朝试锋芒。高…

After Effects 2024 mac/win版:创意视效,梦想起航

After Effects 2024是一款引领视效革命的专业软件&#xff0c;汇聚了创意与技术的精华。作为Adobe推出的全新版本&#xff0c;它以其强大的视频处理和动画创作能力&#xff0c;成为从事设计和视频特技的机构&#xff0c;如电视台、动画制作公司、个人后期制作工作室以及多媒体工…

【Linux】进程信号_2

文章目录 八、进程信号1. 信号 未完待续 八、进程信号 1. 信号 除了可以使用 kill 命令和键盘来生成信号&#xff0c;我们也可以使用系统调用来生成信号。 kill函数可以对指定进程发送指定信号。 使用方法&#xff1a; int main(int argc, char *argv[]) {if (argc ! 3) {c…

利用viztracer进行性能分析和优化

上一篇文章&#xff0c;我们详细讲解了scalene这个性能分析和优化工具的使用流程&#xff1b;今天&#xff0c;我们将深入探讨另一个性能分析和优化工具——viztracer。 什么是viztracer&#xff1f; viztracer是一个非常强大的分析器&#xff0c;可以生成详细的性能报告和可…

信号与系统实验-实验五 离散时间系统的时域分析

一、实验目的 1、理解离散信号的定义与时域特征&#xff0c;掌握在时域求解信号的各种变换运算&#xff1b; 2、掌握离散系统的单位响应及其 MATLAB 实现的方法&#xff1b; 3、掌握离散时间序列卷积及其 MATLAB 实现的方法&#xff1b; 4、掌握利用 MATLAB 求解微分方程&a…