【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

这篇具有很好参考价值的文章主要介绍了【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ExceptionHandler的作用

ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,以捕获不同级别的异常。

在Spring中使用ExceptionHandler非常简单,只需在需要捕获异常的方法上注解@ExceptionHandler,然后定义一个方法,该方法将接收异常并返回异常信息,并将该异常信息展示给前端用户。

ExceptionHandler的使用

说明:针对可能出问题的Controller,新增注解方法@ExceptionHandler,下面是一个基本的ExceptionHandler示例

@RestController
public class ExceptionController {
	
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("An error occurred: " + ex.getMessage());
    }
    @RequestMapping("/test")
    public String test() throws Exception {
        throw new Exception("Test exception!");
    }
}

在上面的示例中,我们定义了一个叫做ExceptionController的类,该类是一个@RestController注解的控制器,它包括一个可以产生异常的请求处理程序,一个用于捕获和处理异常的@ExceptionHandler方法。

@RequestMapping注解配置了一个名为“/test”的API,该API将抛出一个异常,该异常将由我们上面的ExceptionHandler进行处理。当请求“/test”时,Controller方法将引发异常并触发@ExceptionHandler方法。

在上面的@ExceptionHandler方法中,我们通过ResponseEntity将异常信息提供给客户端,HTTP状态码设置为500。这使客户端了解已发生错误,并能够在日志中记录异常信息以便日后调试。

总之,使用ExceptionHandler能够更好的掌控应用的异常信息,使得应用在发生异常的时候更加可控,并且更加容易进行调试

ExceptionHandler的注意事项

  • Controller类下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常。

  • @ExceptionHandler下方法返回值类型支持多种,常见的ModelAndView,@ResponseBody注解标注,ResponseEntity等类型都OK.

源码分析介绍

原理说明-doDispatch

代码片段位于:org.springframework.web.servlet.DispatcherServlet#doDispatch

执行@RequestMapping方法抛出异常后,Spring框架 try-catch的方法捕获异常, 正常逻辑发不发生异常都会走processDispatchResult流程 ,区别在于异常的参数是否为null .

	HandlerExecutionChain mappedHandler = null;
	Exception dispatchException = null;
	ModelAndView mv = null;
    try{
        //根据请求查找handlerMapping找到controller
        mappedHandler=getHandler(request); 
        //找到处理器适配器HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 
        if(!mappedHandler.applyPreHandle(request,response)){ 
            //拦截器preHandle
            return ;
        }      
        //调用处理器适配器执行@RequestMapping方法
        mv=ha.handle(request,response); 
        //拦截器postHandle
        mappedHandler.applyPostHandle(request,response,mv);  
    }catch(Exception ex){
        dispatchException=ex;
    }
    //将异常信息传入了
    processDispatchResult(request,response,mappedHandler,mv,dispatchException) 

原理说明-processDispatchResult

代码片段位于:org.springframework.web.servlet.DispatcherServlet#processDispatchResult

如果 @RequestMapping 方法抛出异常,拦截器的postHandle方法不执行,进入processDispatchResult,判断入参dispatchException,不为null , 代表发生异常,调用processHandlerException处理。

原理说明-processHandlerException

代码片段位于:org.springframework.web.servlet.DispatcherServlet#processHandlerException
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

this当前对象指dispatchServlet,handlerExceptionResolvers可以看到三个HandlerExceptionResolver,这三个是Spring框架帮我们注册的,遍历有序集合handlerExceptionResolvers,调用接口的resolveException方法。

【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

注册的第一个HandlerExceptionResolver.ExceptionHandlerExceptionResolver, 继承关系如下面所示。

【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

原理说明-AbstractHandlerExceptionResolver

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException

【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
这里AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用来记录日志、prepareResponse方法,用来设置response的Cache-Control。
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
异常处理方法就位于doResolveException
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

注意:AbstractHandlerExceptionResolver和AbstractHandlerMethodExceptionResolver名字看起来非常相似,但是作用不同,一个是面向整个类的,一个是面向方法级别的。

原理说明-AbstractHandlerMethodExceptionResolver

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo

接口方法实现AbstractHandlerExceptionResolver的resolveException,先判断shouldApplyTo,AbstractHandlerExceptionResolver 和子类AbstractHandlerMethodExceptionResolver都实现了shouldApplyTo方法,子类的shouldApplyTo都调用父类AbstractHandlerExceptionResolver的shouldApplyTo.

父类AbstractHandlerExceptionResolver的shouldApplyTo方法.

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#shouldApplyTo

Spring初始化的时候并没有额外配置 , 所以mappedHandlers和mappedHandlerClasses都为null, 可以在这块扩展进行筛选 ,AbstractHandlerExceptionResolver提供了setMappedHandlerClasses 、setMappedHandlers用于扩展。

doResolveException

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
Spring请求方法执行一样的处理方式,设置argumentResolvers、returnValueHandlers,之后进行调用异常处理方法。
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

获取@ExceptionHandler

@ExceptionHandler的方法入参支持:Exception ;SessionAttribute 、 RequestAttribute注解、 HttpServletRequest 、HttpServletResponse、HttpSession。
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
@ExceptionHandler方法返回值常见的可以是: ModelAndView 、@ResponseBody注解、ResponseEntity。

getExceptionHandlerMethod方法

getExceptionHandlerMethod说明: 获取对应的@ExceptionHandler方法,封装成ServletInvocableHandlerMethod返回。

exceptionHandlerCache是针对Controller层面的@ExceptionHandler的处理方式,而exceptionHandlerAdviceCache是针对@ControllerAdvice的处理方式. 这两个属性都位于ExceptionHandlerExceptionResolver中。

【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

ExceptionHandlerMethodResolver,缓存A之前没存储过Controller的class ,所以新建一个ExceptionHandlerMethodResolver 加入缓存中,ExceptionHandlerMethodResolver 的初始化工作一定做了某些工作。
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

resolveMethod方法

根据异常对象让 ExceptionHandlerMethodResolver 解析得到 method , 匹配到异常处理方法就直接封装成对象 ServletInvocableHandlerMethod ; 就不会再去走@ControllerAdvice里的异常处理器了,这里说明了。

【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

resolveMethodByExceptionType根据当前抛出异常寻找 匹配的方法,并且做了缓存,以后遇到同样的异常可以直接走缓存取出
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
resolveMethodByExceptionType方法,尝试从缓存A:exceptionLookupCache中根据异常class类型获取Method ,初始时候肯定缓存为空 ,就去遍历ExceptionHandlerMethodResolver的mappedMethods(上面提及了key为异常类型,value为method,exceptionType为当前@RequestMapping方法抛出的异常,判断当前异常类型是不是@ExceptionHandler中value声明的子类或本身,满足条件就代表匹配上了;
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

可能存在多个匹配的方法,使用ExceptionDepthComparator排序,排序规则是按照继承顺序来(继承关系越靠近数值越小,当前类最小为0,顶级父类Throwable为int最大值),排序之后选取继承关系最靠近的那个,并且ExceptionHandlerMethodResolver的exceptionLookupCache中,key为当前抛出的异常,value为解析出来的匹配method.

全局级别异常处理器实现HandlerExceptionResolver接口

public class MyHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelMap mmp=new ModelMap();
        mmp.addAttribute("ex",ex.getMessage());
        return new ModelAndView("error",mmp);
    }
}
  • 使用方式: 只需要将该Bean加入到Spring容器,可以通过Xml配置,也可以通过注解方式加入容器;
    方法返回值不为null才有意义,如果方法返回值为null,可能异常就没有被捕获.

  • 缺点分析:比如这种方式全局异常处理返回JSP、velocity等视图比较方便,返回json或者xml等格式的响应就需要自己实现了.如下是我实现的发生全局异常返回JSON的简单例子.

public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("发生全局异常!");
        ModelMap mmp=new ModelMap();
        mmp.addAttribute("ex",ex.getMessage());
        response.addHeader("Content-Type","application/json;charset=UTF-8");
        try {
            new ObjectMapper().writeValue(response.getWriter(),ex.getMessage());
            response.getWriter().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

全局级别异常处理器@ControllerAdvice+@ExceptionHandler使用方法

用法说明:这种情况下 @ExceptionHandler与第一种方式用法相同,返回值支持ModelAndView,@ResponseBody等多种形式。

@ControllerAdvice
public class GlobalController {
    @ExceptionHandler(RuntimeException.class)
    public ModelAndView fix1(Exception e){
        System.out.println("全局的异常处理器");
        ModelMap mmp=new ModelMap();
        mmp.addAttribute("ex",e);
        return new ModelAndView("error",mmp);
    }
}
  • 方式一:提到ExceptionHandlerExceptionResolver不仅维护@Controller级别的@ExceptionHandler,同时还维护的@ControllerAdvice级别的@ExceptionHandler代码片段位于:
    【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
    isApplicableToBeanType方法是用来做条件判断的,@ControllerAdvice注解有很多属性用来设置条件,
    basePackageClasses、assignableTypes、annotations等,比如我限定了annotations为注解X, 那标注了@X 的ControllerA就可以走这个异常处理器,ControllerB就不能走这个异常处理器。

现在问题的关键就只剩下了exceptionHandlerAdviceCache是什么时候扫描@ControllerAdvice的,下面的逻辑和@ExceptionHandler的逻辑一样了,exceptionHandlerAdviceCache初始化逻辑:

代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet,afterPropertiesSet是Spring bean创建过程中一个重要环节。
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理


代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache

【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

ControllerAdviceBean.findAnnotatedBeans方法查找了SpringMvc父子容器中标注 @ControllerAdvice 的bean, new ExceptionHandlerMethodResolver初始化时候解析了当前的@ControllerAdvice的bean的@ExceptionHandler,加入到ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,key为ControllerAdviceBean,value为ExceptionHandlerMethodResolver . 到这里exceptionHandlerAdviceCache就初始化完毕。

Spring父子容器中所有@ControllerAdivce的bean的方法

代码片段位于:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans

遍历了SpringMVC父子容器中所有的bean,标注ControllerAdvice注解的bean加入集合返回。

比较说明

@Controller+@ExceptionHandler、HandlerExceptionResolver接口形式、@ControllerAdvice+@ExceptionHandler优缺点说明:

调用优先级

  • @Controller+@ExceptionHandler优先级最高
  • @ControllerAdvice+@ExceptionHandler 略低
  • HandlerExceptionResolver最低。

三种方式并存的情况 优先级越高的越先选择,而且被一个捕获处理了就不去执行其他的

三种方式都支持多种返回类型

  • @Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler可以使用Spring支持的@ResponseBody、ResponseEntity。

  • HandlerExceptionResolver方法声明返回值类型只能是 ModelAndView,如果需要返回JSON、xml等需要自己实现.。

缓存利用

  • @Controller+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,

  • HandlerExceptionResolver接口是不做缓存的,在异常报错的情况下才会走自己的HandlerExceptionResolver实现类,多少有点性能损耗.文章来源地址https://www.toymoban.com/news/detail-408738.html

到了这里,关于【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Alibaba中间件技术系列】「RocketMQ技术专题」RocketMQ消息发送的全部流程和落盘原理分析

    RocketMQ目前在国内应该是比较流行的MQ 了,目前本人也在公司的项目中进行使用和研究,借着这个机会,分析一下RocketMQ 发送一条消息到存储一条消息的过程,这样会对以后大家分析和研究RocketMQ相关的问题有一定的帮助。 分析的总体技术范围发送到存储,本文的主要目的是

    2024年02月10日
    浏览(27)
  • 【SpringCloud技术专题】「Gateway网关系列」(1)微服务网关服务的Gateway组件的原理介绍分析

    为什么要有服务网关? 我们都知道在微服务架构中,系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?难道要一个个的去调用吗?很显然这是不太实际的,我们需要有一个统一的接口与这些微服务打交道,这就是我们需要服务网关的原因。 我们

    2024年02月11日
    浏览(33)
  • 【分布式技术专题】「分布式技术架构」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)

    Tomcat的总体结构从外到内进行分布,最大范围的服务容器是Server组件,Service服务组件(可以有多个同时存在),Connector(连接器)、Container(容器服务),其他组件:Jasper(Jasper解析)、Naming(命名服务)、Session(会话管理)、Logging(日志管理)、JMX(Java 管理器扩展服务

    2024年01月24日
    浏览(28)
  • 深入分析Spring的IoC容器:从底层源码探索

    前言: 博主在最近的几次面试中,大中小厂都问到了Spring的ioc容器相关问题,这块知识确实是面试中的重点内容,因此结合所看的书籍,在这篇文章中总结下。该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读,感谢大佬的关注。 如果文章有什么需要改进的地方欢迎

    2024年02月12日
    浏览(38)
  • 【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)

    在阅读了上篇文章《【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)》之后,相信您对HashMap的基本原理和基础结构已经有了初步的认识。接下来,我们将进一步深入探索HashMap的源码,揭示其深层次的技术细节。通过这次解析,您将更深入地

    2024年02月01日
    浏览(32)
  • Spring Boot源码解读与原理剖析:深入探索Java开发的奥秘!

    关注+点赞+评论,评论区回复“Spring Boot源码解读与原理剖析:深入探索Java开发的奥秘!” 每篇最多 评论3条 !!采用抽奖助手自动拉取评论区有效评论送书两本, 开奖时间:9月11号 承载着作者的厚望,掘金爆火小册同名读物 《Spring Boot源码解读与原理剖析》 正式出书!

    2024年02月10日
    浏览(36)
  • 从源码角度深入解析Callable接口

    摘要: 从源码角度深入解析Callable接口,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小。 本文分享自华为云社区《一个Callable接口能有多少知识点?》,作者: 冰 河。 并发编程一直是程序员们比较头疼的,如何编写正确的并发程序相比其他程序来

    2023年04月18日
    浏览(29)
  • 【深入解析spring cloud gateway】06 gateway源码简要分析

    上一节做了一个很简单的示例,微服务通过注册到eureka上,然后网关通过服务发现访问到对应的微服务。本节将简单地对整个gateway请求转发过程做一个简单的分析。 主要流程: Gateway Client向 Spring Cloud Gateway 发送请求 请求首先会被HttpWebHandlerAdapter 进行提取组装成网关上下文

    2024年02月10日
    浏览(29)
  • 深入理解python虚拟机:调试器实现原理与源码分析

    调试器是一个编程语言非常重要的部分,调试器是一种用于诊断和修复代码错误(或称为 bug)的工具,它允许开发者在程序执行时逐步查看和分析代码的状态和行为,它可以帮助开发者诊断和修复代码错误,理解程序的行为,优化性能。无论在哪种编程语言中,调试器都是一

    2023年04月26日
    浏览(39)
  • 前端开发攻略---从源码角度分析Vue3的Propy比Vue2的defineproperty到底好在哪里。一篇文章让你彻底弄懂响应式原理。

    Vue的响应式到底要干什么? 无非就是要知道当你 读取 对象的时候,要知道它读了。要做一些别的事情 无非就是要知道当你 修改 对象的时候,要知道它改了。要做一些别的事情 所以要想一个办法, 把读取和修改的动作变成一个函数 ,读取和修改的时候分别调用对应的函数

    2024年04月17日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包