Spring异常处理三种方式@ExceptionHandler

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Spring异常处理三种⽅式@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 和 AbstractHandler Method ExceptionResolver名字看起来⾮常相似. 这⾥AbstractHandlerExceptionResolver 的shouldApplyTo都返回 true, logException⽤来记录⽇志、prepareResponse⽅法⽤来设置response的Cache-Control. 异常处理⽅法就位于doResolveException.
代码⽚段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo
接⼝⽅法实现是AbstractHandlerExceptionResolver的resolveException,先判断 shouldApplyTo, AbstractHandlerExceptionResolver 和⼦类
AbstractHandler Method ExceptionResolver都实现了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
简单使⽤⽅法:
@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实现类,多少有点性能损耗.。

相关文档
最新文档