Spring 异常处理三种方式 @ExceptionHandler
- 异常处理方式一. @ExceptionHandler
- 异常处理方式二. 实现HandlerExceptionResolver接口
- 异常处理方式三. @ControllerAdvice+@ExceptionHandler
- 三种方式比较说明(强烈推荐各位看一下,我觉得自己总结的比较多,嘿嘿,不对之处请指出,点我快速前往!)
问题描述: 假如对异常不进行处理?
假如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,加入到ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,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的更多相关文章
- 一步一步深入spring(2)-三种方式来实例化bean
在一步一步深入spring(1)--搭建和测试spring的开发环境中提到了一种实例化bean的方式,也是最基本的使用构造器实例化bean 1.使用构造器实例化bean:这是最简单的方式,Spring ...
- Spring MVC异常统一处理的三种方式
Spring 统一异常处理有 3 种方式,分别为: 使用 @ ExceptionHandler 注解 实现 HandlerExceptionResolver 接口 使用 @controlleradvi ...
- 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】
一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...
- Spring的三种通过XML实现DataSource注入方式
Spring的三种通过XML实现DataSource注入方式: 1.使用Spring自带的DriverManagerDataSource 2.使用DBCP连接池 3.使用Tomcat提供的JNDI
- spring ioc三种注入方式
spring ioc三种注入方式 IOC ,全称 (Inverse Of Control) ,中文意思为:控制反转 什么是控制反转? 控制反转是一种将组件依赖关系的创建和管理置于程序外部的技术. 由容 ...
- Spring依赖注入三种方式详解
在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...
- Spring的依赖注入(DI)三种方式
Spring依赖注入(DI)的三种方式,分别为: 1. 接口注入 2. Setter方法注入 3. 构造方法注入 下面介绍一下这三种依赖注入在Spring中是怎么样实现的. 首先我们需要以下几个 ...
- spring入门:beans.xml不提示、别名、创建对象的三种方式
spring的版本是2.5 一.beans.xml文件不提示 Location:spring-framework-2.5.6.SEC01\dist\resources\spring-beans-2.5 ...
- SSH深度历险记(八) 剖析SSH核心原则+Spring依赖注入的三种方式
于java发育.一类程序猿必须依靠类的其他方法,它是通常new依赖类的方法,然后调用类的实例,这样的发展问题new良好的班统一管理的例子.spring提出了依赖注入的思想,即依赖类不由程 ...
随机推荐
- 深入JVM之类的加载器
类加载器有两种: —java虚拟机的自带加载器 根类加载器(Bootstrap) 扩展类加载器(Extension) 系统类加载器(AppClassLoder) —自定义的类加载器 java.lang ...
- [leetcode]57. Insert Interval插入区间
Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessa ...
- [leetcode]16. 3Sum Closest最接近的三数之和
Given an array nums of n integers and an integer target, find three integers in nums such that the s ...
- 微软Office Online服务安装部署(二)
现在准备配置Client 1.进入到桌面后,打开powershell 输入: Add-WindowsFeature Web-Server,Web-Mgmt-Tools,Web-Mgmt-Console ...
- oracle 导入DMP文件时IMP-00013: 只有 DBA 才能导入由其他 DBA 导出的文件 IMP-00000: 未成功终止导入
参考: https://blog.csdn.net/breaker892902/article/details/11004495 给要导入的用户授权 插入成功
- C# NPOI 日期格式
之前整理的NPOI导入导出Excel 在之前使用过程中没发现问题. 但是后来发现导入的文档如果有日期时间格式,导入时会有混乱 后来找了一下解决方案,最终将其中一段修改即可导入日期(导出未测试) 原因 ...
- 29.Mysql监控
29.Mysql监控29.1 如何选择一个监控方案 29.1.1 选择何种监控方式 29.1.2 如何选择合适自己的监控工具29.2 常用网络监控工具 29.2.1 Cacti简介 29.2.2 Na ...
- python 之字符编码
一 了解字符编码的储备知识 python解释器和文件本编辑的异同 相同点:python解释器是解释执行文件内容的,因而python解释器具备读py文件的功能,这一点与文本编辑器一样 不 ...
- 20172306 2018-2019-2 《Java程序设计》第五周学习总结
20172306 2018-2019-2 <Java程序设计>第五周学习总结 教材学习内容总结 查找 查找中,我们对这些算法的实现就是对某个Comparable对象的数组进行查找 泛型声明 ...
- 73.解决Xcode10 library not found for -lstdc++ 找不到问题
Xcode10 彻底废除了libstdc++,相关文件libstdc++.6.0.9.dylib.libstdc++.6.dylib.libstdc++.dylib.libstdc++.6.0.9.t ...