源码剖析Springboot自定义异常

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

 1 @ControllerAdvice2 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     @Override2     public void afterPropertiesSet() {3         // Do this first, it may add ResponseBodyAdvice beans4         //走这里初始化,添加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 = BeanFactoryUtils9                     .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.mzph.cn/news/861238.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kotlin require和assert 区别

在 Kotlin 中&#xff0c;require 和 assert 是两种用于验证条件的方法&#xff0c;主要区别在于它们的使用场景和触发机制。 require require 用于函数参数的验证。如果条件不满足&#xff0c;它会抛出 IllegalArgumentException 异常。这通常用于对公共 API 的输入参数进行…

Yarn Workspaces 深度解析:统一管理多项目结构的利器

Yarn 是一个现代的包管理器&#xff0c;它在 JavaScript 生态系统中起着至关重要的作用。Yarn Workspaces 是 Yarn 的一项功能&#xff0c;允许开发者在单一的工作区内管理多个包或项目。这种多项目工作流在开发大型应用程序或库时尤其有用&#xff0c;因为它可以简化依赖管理和…

从命令行管理文件——命名规则,硬连接

1. 文件命名规则&#xff1a; 不能使用 / 来当文件名&#xff0c; / 是用来做根的&#xff0c;也是用来做路径分隔符的 文件名不能超过 255 个字符 区分大小写 file File FILE fIle root Root ROOT rOOt ROOT 目录也是文件&#xff0c;在同一路径下&#xff0c;两个不同类…

VNode是什么?

什么是VNode VNode的全称是Virtual Node,也就是虚拟节点.它是指一个抽象的节点对象&#xff0c;用于描述真实DOM中的元素。在前端框架中&#xff0c;通过操作VNode来实现虚拟DOM&#xff0c;从而提高性能。 VNode的本质 本质上是JavaScript对象,这个对象就是更加轻量级的对DOM…

越有水平的领导,越擅长用这3个字来管人,怪不得执行力强

越有水平的领导&#xff0c;越擅长用这3个字来管人&#xff0c;怪不得执行力强 第一个字&#xff1a;“实” 要想提高执行力&#xff0c;必须发扬务实、实干、刻苦勤勉的工作精神。纸上谈兵&#xff0c;夸夸其谈的事情少做&#xff0c;多行动&#xff0c;少说话。 沉浸在表面…

打破数据分析壁垒:SPSS复习必备(十一)

一、方差分析 方差分析的应用条件如下&#xff1a; &#xff08;1&#xff09;独立&#xff0c;各组数据相互独立&#xff0c;互不相关&#xff1b; &#xff08;2&#xff09;正态&#xff1a;即各组数据符合正态分布&#xff1b; &#xff08;3&#xff09;方差齐性&…

多线程思维导图

多线程 线程是一个程序内部的一条执行流程 多线程的好处————消息通信&#xff0c;网页浏览等等 多线程是指从软硬件上实现多条执行流程的技术 并发和并行同时执行 多线程的创建 Java.Long包下的Thread类 定义一个子类…

突然断供中国!OpenAI变CloseAI,用户连夜搬家

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 OpenAI&#xff0c;这把变成CloseAI了。 6月25日早上&#xff0c;有中国开发者表示收到了来自OpenAI的“警告信”&#xff1a;将采取额外措施停止其不支持的地区的API&#xff08;应用接口&#xff09…

我对AI赋能的未来畅想

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

【Java Web】XML格式文件

目录 一、XML是什么 二、常见配置文件类型 *.properties类型&#xff1a; *.xml类型&#xff1a; 三、DOM4J读取xml配置文件 3.1 DOM4J的使用步骤 3.2 DOM4J的API介绍 一、XML是什么 XML即可扩展的标记语言&#xff0c;由标记语言可知其基本语法和HTML一样都是由标签构成的文件…

Docker: 使用容器化数据库

使用容器化数据库 使用本地容器化数据库提供了灵活性和简易的设置,使您能够在不需要传统数据库安装开销的情况下,紧密模拟生产环境。Docker 简化了这一过程,只需几条命令就可以在隔离的容器中部署、管理和扩展数据库。 在本指南中,您将学习如何: 运行本地容器化数据库访…

AppConfig文件中加入自定义的节点

1.需要添加System.Configuration.dll引用&#xff0c;才可以在C#中读取appConfig文件. <?xml version"1.0" encoding"utf-8"?> <configuration> <startup> <supportedRuntime version"v4.0" sku".NETF…

Java测试类

在Java中&#xff0c;为了编写测试类&#xff0c;通常使用JUnit框架。 1. 首先&#xff0c;创建一个名为Calculator的简单Java类&#xff0c;它包含一个方法add用于计算两个整数的和&#xff1a; public class Calculator {public int add(int a, int b) {return a b;} } 2.…

springboot vue 开源 会员收银系统 (8) 收银台、开卡结算及订单的优化升级

前言 完整版演示 开发版演示 在之前的开发进程中&#xff0c;我们基本搭建了收银台的基础。这次着重梳理一下收银台相关功能的开发及优化情况。 1.会员查询与开卡 收银台新增加了会员筛选功能 并且会员和会员卡是一对多的关系 理论可以开无数张卡 默认选择一张卡 会员卡选择…

stm32学习笔记---TIM输出比较(代码部分)PWM驱动LED呼吸灯/舵机/直流电机

目录 第一个工程&#xff1a;PWM驱动LED呼吸灯 PWM.c 初始化PWM步骤 TIM的库函数 TIM_OCStructInit TIM_CtrlPWMOutputs TIM_CCxCmd和TIM_CCxNCmd TIM_SelectOCxM 四个单独更改CCR寄存器值的函数 四个初始化定时器的通道的函数 给结构体一次性都赋初始值的函数 如何…

从写下第1个脚本到年薪40W,我的测试开发心路历程!

对于任何职业来说&#xff0c;薪资始终都会是众多追求的重要部分。前几年测试行业还是风口&#xff0c;但是随着不断新鲜血液的加入&#xff0c;再加上就业大环境不好&#xff0c;企业也都在“降本增效”。目前内卷也是越来越激烈。不得不承认当下的现状&#xff0c;已经不仅仅…

引导过程与服务器控制

一、引导过程 1.开机自检 服务器主机开机以后&#xff0c;将根据主板 BIOS 中的设置对 CPU&#xff08;Central Processing Unit&#xff0c; 中央处理器&#xff09;、内存、显卡、键盘等设备进行初步检测&#xff0c;检测成功后根据预设的启动顺序移 交系统控制权&#xff0c…

window.location.pathname和window.location.href

1.window.location.pathname window.location.pathname 是一个 JavaScript 属性&#xff0c;它返回当前页面的 URL 中的路径部分。这个属性是 window.location 对象的一部分&#xff0c;window.location 对象包含了当前页面的 URL 信息。 例如&#xff0c;如果你的页面 URL 是…

AI X HI:塑造数智时代的人类镜像,网易这场分享不能错过!

2001 年&#xff0c;网易正式成立在线游戏事业部。从那以后&#xff0c;网易孵化了许多出圈的精品游戏&#xff0c;跻身成为全球七大游戏公司之一。这些游戏产品之所以能够广受玩家好评&#xff0c;并保持常青&#xff0c;一方面源于十年磨一剑的精良品质&#xff0c;另一方面则…

[油猴脚本] Image To Ascii 快速转换审计网站图片中敏感信息插件

项目地址:https://github.com/MartinxMax/ImageToAscii 导入 将ImagetoAscii.user.js导入油猴 进行按照 访问网站分析图片 当鼠标靠近图片时会出现分析按钮 通过审查图片信息,我们可以快速发现这张图片存在PHP代码。 当然在渗透测试中,你可以快速查看上传的图片木马中PHP代码…