Spring 异常处理的三种方式 整理

2023-11-05

  1. 异常处理方式一. @ExceptionHandler
  2. 异常处理方式二. 实现HandlerExceptionResolver接口
  3. 异常处理方式三. @ControllerAdvice+@ExceptionHandler
  4. 三种方式比较说明(强烈推荐各位看一下,我觉得自己总结的比较多,嘿嘿,不对之处请指出,点我快速前往!)

 

问题描述: 假如对异常不进行处理?

假如SpringMvc我们不对异常进行任何处理, 界面上显示的是这样的.

image

 

异常处理的方式有三种:

1|0一. Controller层面上异常处理 @ExceptionHandler

说明:针对可能出问题的Controller,新增注解方法@ExceptionHandler.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

@Controller

@RequestMapping("/testController")

public class TestController {

 

    @RequestMapping("/demo1")

    @ResponseBody

    public Object demo1(){

        int i = 1 / 0;

        return new Date();

    }

 

    @ExceptionHandler({RuntimeException.class})

    public ModelAndView fix(Exception ex){

        System.out.println("do This");

        return new ModelAndView("error",new ModelMap("ex",ex.getMessage()));

    }

}

注意事项: 1. 一个Controller下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常.

Ambiguous @ExceptionHandler method mapped for;

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

原理说明:

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

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

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

HandlerExecutionChain mappedHandler = null;

    Exception dispatchException = null;

    ModelAndView mv = null;

    try{

         

        mappedHandler=getHandler(request); //根据请求查找handlerMapping找到controller

        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//找到处理器适配器HandlerAdapter

         

        if(!mappedHandler.applyPreHandle(request,response)){ //拦截器preHandle

            return ;

        }      

        mv=ha.handle(request,response); //调用处理器适配器执行@RequestMapping方法

        mappedHandler.applyPostHandle(request,response,mv);  //拦截器postHandle

    }catch(Exception ex){

        dispatchException=ex;

    }

        processDispatchResult(request,response,mappedHandler,mv,dispatchException) //将异常信息传入了

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

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

image

 

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

this当前对象指dispatchServlet,handlerExceptionResolvers可以看到有三个HandlerExceptionResolver, 这三个是<mvc:annotation-driven />帮我们注册的.  遍历有序集合handlerExceptionResolvers,调用接口的resolveException方法. 

image

 

记录<mvc:annotation-driven/>注册的第一个 HandlerExceptionResolver : ExceptionHandlerExceptionResolver,  继承关系如下面所示.

image

 

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

AbstractHandlerExceptionResolver 和 AbstractHandlerMethodExceptionResolver名字看起来非常相似. 这里AbstractHandlerExceptionResolver 的shouldApplyTo都返回 true, logException用来记录日志、prepareResponse方法用来设置response的Cache-Control.    异常处理方法就位于doResolveException.

 

image

 

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

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

image

 

查看父类AbstractHandlerExceptionResolver的shouldApplyTo方法.

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

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

image

 

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

image

 

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

似曾相识的ServletInvocableHandlerMethod,getExceptionHandlerMethod目的就是获取 针对异常的处理方法,没找到的话这里就直接返回了,找到了执行异常处理方法;

之后同Spring请求方法执行一样的处理方式,设置argumentResolvers、returnValueHandlers,之后进行调用异常处理方法,

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

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

image

 

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

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

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

handlerType指代Controller的class属性,尝试从缓存A exceptionHandlerCache 中根据controller的class  查找ExceptionHandlerMethodResolver;  缓存A之前没存储过Controller的class  ,所以新建一个ExceptionHandlerMethodResolver 加入缓存中.  ExceptionHandlerMethodResolver 的初始化工作一定做了某些工作!

resolveMethod方法:根据异常对象让  ExceptionHandlerMethodResolver 解析得到 method , 匹配到异常处理方法  就直接封装成对象 ServletInvocableHandlerMethod ; 就不会再去走@ControllerAdvice里的异常处理器了.  这里说明了,ExceptionHandlerMethodResolver 初始化的时候完成存储 @ExceptionHandler.

image

 

查看ExceptionHandlerMethodResolver 初始化工作内容:

代码片段位于:org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver

handlerType为传入的Controller的class属性,通过EXCEPTION_HANDLER_METHODS选出 class 中标注@ExceptionHandler的方法,解析@Exception注解的value值(class类型的数组),并加入到当前ExceptionHandlerMethodResolver的mappedMethods集合中,key为 异常类型 ,value为 method.

如果@ExceptionHandler的 value属性为空,就会将方法入参中的Throwable的子类作为异常类型.  @ExceptionHandler的value属性和方法入参不能同时都为空,否则会抛出异常.

image

image

image

image

 

ExceptionHandlerMethodResolver完成了初始化工作,如何根据当前发生异常类型查找到对应方法?

代码片段位于:org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#resolveMethod

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

 

image

 

代码片段位于:org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#resolveMethodByExceptionType

resolveMethodByExceptionType方法,尝试从缓存A:exceptionLookupCache中根据 异常class类型获取Method ,初始时候肯定缓存为空 ,就去 遍历ExceptionHandlerMethodResolver的mappedMethods(上面提及了key为异常类型,value为method),  exceptionType为当前@RequestMapping方法抛出的异常,判断当前异常类型是不是@ExceptionHandler中value声明的子类或本身,满足条件就代表匹配上了;可能存在多个匹配的方法,使用ExceptionDepthComparator排序,排序规则是按照继承顺序来(继承关系越靠近数值越小,当前类最小为0,顶级父类Throwable为int最大值),排序之后选取继承关系最靠近的那个,并且存入ExceptionHandlerMethodResolver的exceptionLookupCache中,key为当前抛出的异常,value为解析出来的匹配method.

image

 

至此 @ExceptionHandler Spring读取到并解析出来完毕了,后续流程和Spring正常请求流程一样,包括@ExceptionHandler的方法入参、方法返回值.

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

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

 

2|0二. 全局级别异常处理器 实现HandlerExceptionResolver接口

01

02

03

04

05

06

07

08

09

10

11

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());

        return new ModelAndView("error",mmp);

    }

 

}

使用方式: 只需要将该Bean加入到Spring容器,可以通过Xml配置,也可以通过注解方式加入容器;  

              方法返回值不为null才有意义,如果方法返回值为null,可能异常就没有被捕获.

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

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

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();

    }

 

}

原理分析:记得之前介绍了 DispatcherServlet的HandlerExceptionResolver集合,这种方式的HandlerExceptionResolver就是从DispatcherServlet的HandlerExceptionResolver集合入手的.

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

this对象指代DispatcherServlet,和上面方式对比,发现我们只是将MyHandlerExceptionResolver 加入到Spring容器,dispatchServlet 的 handlerExceptionResolvers属性就多了我们自己定义的全局异常解析器;

ExceptionHandlerMethodResolver是用来解析@Controller层面的@ExceptionHandler注解,当前Controller没有找到@ExceptionHandler来处理自己抛出的异常,才遍历下一个HandlerExceptionResolver;

HandlerExceptionResolver是个有序集合,Spring注册的HandlerExceptionResolver调用resolveException都失败之后,才轮到我们自定义的MyHandlerExceptionResolver ;而且我们自定义的MyHandlerExceptionResolver 就没法使用SpringMvc的注解等等.

image

 

我们只是将HandlerExceptionResolver加入到Spring容器中,Spring是如何通知给DispatcherServlet呢?

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

initHandlerExceptionResolvers只是DispatcherServlet初始化策略方法initStrategies中的一小步,可以看到只要是SpringMvc父子容器中注册的HandlerExceptionResolver类型实例,DispatcherServlet都会自动将其加入到DispatcherServlet的handlerExceptionResolvers中. 所以我们需要做的只是实现HandlerExceptionResolver接口,并且纳入Spring容器管理即可.

image

 

3|0三.全局级别异常处理器 @ControllerAdvice+@ExceptionHandler

简单使用方法:

01

02

03

04

05

06

07

08

09

10

11

@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);

    }

}

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

 

方式一提到ExceptionHandlerExceptionResolver不仅维护@Controller级别的@ExceptionHandler,同时还维护的@ControllerAdvice级别的@ExceptionHandler.

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

  isApplicableToBeanType方法是用来做条件判断的,@ControllerAdvice注解有很多属性用来设置条件,basePackageClasses、assignableTypes、annotations等,比如我限定了annotations为注解X, 那标注了@X 的ControllerA就可以走这个异常处理器,ControllerB就不能走这个异常处理器.

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

image

 

exceptionHandlerAdviceCache初始化逻辑:

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

afterPropertiesSet是Spring bean创建过程中一个重要环节. 

image

 

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

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

image

 

查找SpringMvc父子容器中所有@ControllerAdivce的bean的方法

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

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

image

 

4|0四.比较说明.

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

在Spring4.3.0版本下,1.优先级来说,@Controller+@ExceptionHandler优先级最高,其次是@ControllerAdvice+@ExceptionHandler,最后才是HandlerExceptionResolver,说明假设三种方式并存的情况 优先级越高的越先选择,而且被一个捕获处理了就不去执行其他的.

                                2. 三种方式都支持多种返回类型,@Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler可以使用Spring支持的@ResponseBody、ResponseEntity,而HandlerExceptionResolver方法声明返回值类型只能是 ModelAndView,如果需要返回JSON、xml等需要自己实现.

                                3.缓存利用,@Controller+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中, 而HandlerExceptionResolver接口是不做缓存的,在前面两种方式都fail的情况下才会走自己的HandlerExceptionResolver实现类,多少有点性能损耗.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 异常处理的三种方式 整理 的相关文章

随机推荐

  • QT设计电子时钟类

    1 界面效果 2 类的声明 wedgit h class Widget public QWidget Q OBJECT public Widget QWidget parent nullptr 默认构造函数 Widget 默认析构函数 vo
  • 磁共振检查头部能检测出什么_头部核磁共振可以检查什么?

    很多人会疑惑为什么要做头部核磁共振 做CT不好吗 这是因为脑CT具有一定的局限性 有时候脑CT是不能诊断出脑部异常情况的 头部核磁共振检査较CT更为敏感 具有多方向切层 多参数成像的特点 能更精确的现实病变位置 范围大小及组织学特性 是发现
  • 【Java】利用SpringBoot搭建WebService服务接口

    前言 在项目开发过程中经常会碰到对接医疗软件系统的时候对方要求提供WebService形式的接口 本篇文章记载了个人对接项目过程中整合并搭建的WebService形式的接口 希望对您能够有所帮助 一 在pom xml文件中导入WebServ
  • 期权、期货及其他衍生产品 第一章读书笔记

    期权 期货及其他衍生产品 第一章读书笔记 介绍 什么是衍生产品 衍生产品的特点 有哪些交易所场所 交易所市场 一些著名的交易所市场 场外市场 我国的场外市场 远期合约 远期合约可以用来对冲外汇风险 远期合约的收益 远期价格和即期价格 期货合
  • lua学习笔记—table

    1 什么是table table是lua的一种数据结构 可以用来创建数组或映射 lua中的table使用的是关联型数组 关联数组的key值可以是除过nil之外任意类型的值 table的大小是不固定的 可以自己进行扩容 2 如何构造table
  • iOS autorelease 示例研究

    iOS autorelease是Objective C中的一个自动内存管理机制 它通过在对象创建时将其添加到自动释放池中 在池被释放时自动释放对象 从而减少手动内存管理的工作量 本文将介绍如何使用autorelease机制来管理内存 aut
  • 跨部门的高效沟通与协作

    在企业管理当中 沟通是一个非常重要的技能 它运用我们管理当中每一个细节 首先要做好沟通和协作 我们需要有一个很好的思维模式 这个就像盖房子一样 它是地基 是一个房子的地基部分 没有一个正确的思维模式 那我们其后的技巧都不会有一个很好的效果
  • 大数据框架总结

    hdfs 1 写数据流程 2 HDFS读数据流程1 3 HDFS副本节点选择 4 HDFS nn 2nn 镜像文件以及编辑日志的工作机制 注意此类机制都是先更新编辑日志 再更新内存文件block元数据 checkpoint触发默认条件是一小
  • 谈谈虚幻引擎4的Global Illumination

    本届GDC 2013 Epic再次展示了UE4的最新demo 效果惊艳毋庸置疑 不过今天我们只谈UE4的光照利器 SVOGI SVOGI全称Sparse Voxel Octree Global Illumination 由Epic的Andr
  • DES的加密与解密(C语言实现)——大三密码学实验

    目录 DES的描述 Feistel体制 密钥扩展函数 F函数 总流程 代码 get函数的构建 yihuo函数的构建 fuck函数的构建 left move函数的构建 exchange函数的构建 erzhuanshi函数的构建 shizhua
  • 利用Vulnhub复现漏洞 - GoAhead 远程命令执行漏洞(CVE-2017-17562)

    GoAhead 远程命令执行漏洞 CVE 2017 17562 Vulnhub官方复现教程 漏洞原理 复现漏洞 启动环境 漏洞复现 动态链接库源码 编译so文件 发送payload Vulnhub官方复现教程 https vulhub or
  • EasyExcel导出模板实现下拉选(解决下拉超过50个限制)

    学习地址 https d9bp4nr5ye feishu cn wiki O3obweIbgi2Rk1ksXJncpClTnAf B站视频 https www bilibili com video BV1H34y1T7Lm 先来看看最终实现
  • MySQL基本操作语句

    目录 基本的操作数据库的语句 操作库的基本SQL语句 针对表的基本SQL语句 针对记录的基本SQL语句 扩展知识 select 标准用法 基本的操作数据库的语句 show databases gt gt gt gt 查看所有的数据库 sho
  • vue3 nvm配置多个版本node

    在实际开发中 我们可能会负责多个项目 有的项目是vue2版本开发的 有的是vue3版本开发的 如果我们电脑全局的node版本是低版本的 那么高版本的vue3项目在安装依赖时就会报错 反之亦然 我们可以使用nvm来安装多个版本的node 并使
  • Vue table不分页 动态加载数据(类似手机端滑动到底端后再去获取数据)

    最近接到一个需求 pc端中的table 数据不做分页 而是做成滚动条形式 但是table中的数据还是一次显示50条 等这50条滑动到底部后 再去加载50条 有加载效果 以此类推 直到数据全部展示 值得注意的是 我的需求是 第一次请求数据就将
  • 在线UTF-8/GBK互相转换工具

    在线UTF 8 GBK互相转换工具 https encode guiboweb com
  • shell执行Oracle SQL并捕获异常案例分析

    一 shell脚本 实现功能读取指定配置文件中的Oracle数据库连接 清空传入变量表的数据 以及清除数据之后的结果进行捕获分析 bin bash Created Date 2022 12 16 Author Last Modified 2
  • db2错误代码

    DB2错误代码 SQL返回码信息对照 用COBOL链接DB2时 出现DB2错误信息时 如果你不懂代码是什么意思 可以用这份资料查找 当然你也可以直接在db2的命令行下输入 db2 SQL30081N 系统会给出一些提示信息 sqlcode
  • LiDAR SLAM的比较

    在自动驾驶领域 定位是很重要的一环 为了建立更有鲁棒性 精确的定位 在实际自动驾驶车上往往都会使用激光雷达 激光雷达相比于摄像头 对光照变化不敏感 适合白天和黑夜 绝大多数路况 激光雷达获得的距离信息精度很高 获取的feature很稳定 当
  • Spring 异常处理的三种方式 整理

    异常处理方式一 ExceptionHandler 异常处理方式二 实现HandlerExceptionResolver接口 异常处理方式三 ControllerAdvice ExceptionHandler 三种方式比较说明 强烈推荐各位看