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

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

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

异常处理的方式有三种:

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

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

@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 .

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处理,

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

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

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

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

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

代码片段位于: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用于扩展.

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

代码片段位于: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;

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.

查看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属性和方法入参不能同时都为空,否则会抛出异常.

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

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

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

代码片段位于: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.

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

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

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

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

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的简单例子.

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的注解等等.

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

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

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

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

简单使用方法:

@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的逻辑一样了.

exceptionHandlerAdviceCache初始化逻辑:

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

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

代码片段位于: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就初始化完毕.

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

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

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

四.比较说明.

@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实现类,多少有点性能损耗.

Spring 异常处理三种方式 @ExceptionHandler的更多相关文章

  1. 一步一步深入spring(2)-三种方式来实例化bean

    在一步一步深入spring(1)--搭建和测试spring的开发环境中提到了一种实例化bean的方式,也是最基本的使用构造器实例化bean 1.使用构造器实例化bean:这是最简单的方式,Spring ...

  2. Spring MVC异常统一处理的三种方式

    Spring 统一异常处理有 3 种方式,分别为: 使用 @ ExceptionHandler 注解 实现 HandlerExceptionResolver 接口 使用 @controlleradvi ...

  3. 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

    一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...

  4. Spring的三种通过XML实现DataSource注入方式

    Spring的三种通过XML实现DataSource注入方式: 1.使用Spring自带的DriverManagerDataSource 2.使用DBCP连接池 3.使用Tomcat提供的JNDI

  5. spring ioc三种注入方式

    spring ioc三种注入方式 IOC ,全称 (Inverse Of Control) ,中文意思为:控制反转 什么是控制反转? 控制反转是一种将组件依赖关系的创建和管理置于程序外部的技术. 由容 ...

  6. Spring依赖注入三种方式详解

    在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...

  7. Spring的依赖注入(DI)三种方式

    Spring依赖注入(DI)的三种方式,分别为: 1.  接口注入 2.  Setter方法注入 3.  构造方法注入 下面介绍一下这三种依赖注入在Spring中是怎么样实现的. 首先我们需要以下几个 ...

  8. spring入门:beans.xml不提示、别名、创建对象的三种方式

    spring的版本是2.5 一.beans.xml文件不提示 Location:spring-framework-2.5.6.SEC01\dist\resources\spring-beans-2.5 ...

  9. SSH深度历险记(八) 剖析SSH核心原则+Spring依赖注入的三种方式

           于java发育.一类程序猿必须依靠类的其他方法,它是通常new依赖类的方法,然后调用类的实例,这样的发展问题new良好的班统一管理的例子.spring提出了依赖注入的思想,即依赖类不由程 ...

随机推荐

  1. EOS的发币逻辑

    [EOS的发币逻辑] EOS官网的Guide中(参考[1]),描述了如何发自己的Token: 1.创建一个contract. 2.有一些create.transfer.close action. 3. ...

  2. Oracle一次Insert多条数据

    insert all into JK_TB_DATE (fbmmc,fgzjh,fsbmc,fsbxh,fsbbh,db_shuifenyi,db_pihao,db_wuliaobianma) ',' ...

  3. Python+Selenium学习--自动生成HTML测试报告

    前言 在脚本运行完成之后,除了在log.txt 文件看到运行日志外,我们更希望能生一张漂亮的测试报告来展示用例执行的结果.        HTMLTestRunner 是Python 标准库的unit ...

  4. toastr简单用法及修改垂直居中

    toastr是一个基于Jquery简单.漂亮的消息提示插件,使用简单.方便,可以根据设置的超时时间自动消失. 1.使用很简单,首选引入toastr的js.css文件html <script sr ...

  5. Power designer 的使用

    1.Powere Designer 逆向 工程 首先 逆向工程 就是将数据库表 导入到模型, 首先新建个模型, 此处就省略 ... 工具栏,数据库(database) 下的 update model ...

  6. Java中两个线程是否可以同时访问同一个对象的两个不同的synchronized方法?

    不可以!!! 多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本 ...

  7. Job for ssh.service failed because the control process exited with error code.......

    转载自:https://blog.csdn.net/woailyoo0000/article/details/79782986 笔者最近更新ubuntu系统,在更新之前设置了证书信任文件,重启以后ss ...

  8. 安装bazel(syntaxnet依赖工具)

    1.简介   Bazel是一个类似于Make的工具,是Google为其内部软件开发的特点量身定制的工具,如今Google使用它来构建内部大多数的软件.它的功能有诸多亮点: 多语言支持:目前Bazel默 ...

  9. JavaScript 高阶函数

    高阶函数的英文叫Higher-order function ,什么是高阶函数呢>? JavaScript的函数其实都指向某个变量.既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接 ...

  10. RQNOJ 1 明明的随机数

    查重和排序,这里我用的set进行存储数据,利用了set的唯一性和自动性,方便了很多 #include <iostream> using namespace std; #include &l ...