相关文章推荐
骑白马的香菜  ·  Java ...·  3 月前    · 
知识渊博的热带鱼  ·  这才是 SpringBoot ...·  1 月前    · 
傲视众生的玉米  ·  不同数据库查询前一小时、前一天、前一周、前三 ...·  1 年前    · 
苦恼的椰子  ·  SpringBoot+Quartz图形化(有 ...·  2 年前    · 
深情的毛豆  ·  Oracle 19C ...·  2 年前    · 
冲动的登山鞋  ·  vector的几种初始化及赋值方式-阿里云开 ...·  2 年前    · 
傲视众生的芹菜  ·  有趣的 tee:Linux 、Go 以及 ...·  2 年前    · 
Code  ›  【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理开发者社区
spring框架 技术原理 response 异常处理
https://cloud.tencent.com/developer/article/2260723?areaId=106001
霸气的领结
1 年前
作者头像
洛神灬殇
0 篇文章

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

原创
前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > 全方位技术攻关 > 【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

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

原创
作者头像
洛神灬殇
发布 于 2023-04-09 17:13:35
238 0
发布 于 2023-04-09 17:13:35
举报

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;
        //根据请求查找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

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

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

原理说明-AbstractHandlerExceptionResolver

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

这里AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用来记录日志、prepareResponse方法,用来设置response的Cache-Control。

异常处理方法就位于doResolveException

注意: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请求方法执行一样的处理方式,设置argumentResolvers、returnValueHandlers,之后进行调用异常处理方法。

获取@ExceptionHandler

@ExceptionHandler的方法入参支持:Exception ;SessionAttribute 、 RequestAttribute注解、 HttpServletRequest 、HttpServletResponse、HttpSession。

@ExceptionHandler方法返回值常见的可以是: ModelAndView 、@ResponseBody注解、ResponseEntity。

getExceptionHandlerMethod方法

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

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

ExceptionHandlerMethodResolver,缓存A之前没存储过Controller的class ,所以新建一个ExceptionHandlerMethodResolver 加入缓存中,ExceptionHandlerMethodResolver 的初始化工作一定做了某些工作。

resolveMethod方法

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

resolveMethodByExceptionType根据当前抛出异常寻找 匹配的方法,并且做了缓存,以后遇到同样的异常可以直接走缓存取出

resolveMethodByExceptionType方法,尝试从缓存A:exceptionLookupCache中根据异常class类型获取Method ,初始时候肯定缓存为空 ,就去遍历ExceptionHandlerMethodResolver的mappedMethods(上面提及了key为异常类型,value为method,exceptionType为当前@RequestMapping方法抛出的异常,判断当前异常类型是不是@ExceptionHandler中value声明的子类或本身,满足条件就代表匹配上了;

可能存在多个匹配的方法,使用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配置,也可以通过注解方式加入容器;undefined方法返回值不为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);
 
推荐文章
骑白马的香菜  ·  Java Blob数据持久化转换存储文件技巧探讨 -
3 月前
知识渊博的热带鱼  ·  这才是 SpringBoot 统一登录鉴权、异常处理、数据格式 的正确姿势_springboot controlleradvice+拦截器失效接口鉴权
1 月前
傲视众生的玉米  ·  不同数据库查询前一小时、前一天、前一周、前三十天数据_pg 计算前一个周_icemeco的博客-CSDN博客
1 年前
苦恼的椰子  ·  SpringBoot+Quartz图形化(有源码) - 掘金
2 年前
深情的毛豆  ·  Oracle 19C 安装指引_chesterchai的博客-CSDN博客
2 年前
冲动的登山鞋  ·  vector的几种初始化及赋值方式-阿里云开发者社区
2 年前
傲视众生的芹菜  ·  有趣的 tee:Linux 、Go 以及 Java - 墨天轮
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号