共计 8523 个字符,预计需要花费 22 分钟才能阅读完成。
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;
try{
// 依据申请查找 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 配置,也能够通过注解形式退出容器;
办法返回值不为 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);
return new ModelAndView("error",mmp);
}
}
- 形式一:提到 ExceptionHandlerExceptionResolver 不仅保护 @Controller 级别的 @ExceptionHandler,同时还保护的 @ControllerAdvice 级别的 @ExceptionHandler 代码片段位于:
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 就初始化结束。
Spring 父子容器中所有 @ControllerAdivce 的 bean 的办法
代码片段位于:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans
遍历了 SpringMVC 父子容器中所有的 bean,标注 ControllerAdvice 注解的 bean 退出汇合返回。
比拟阐明
@Controller+@ExceptionHandler、HandlerExceptionResolver 接口模式、@ControllerAdvice+@ExceptionHandler 优缺点阐明:
调用优先级
- @Controller+@ExceptionHandler优先级最高
- @ControllerAdvice+@ExceptionHandler 略低
- HandlerExceptionResolver最低。
三种形式并存的状况 优先级越高的越先抉择,而且被一个捕捉解决了就不去执行其余的。
三种形式都反对多种返回类型
- @Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler 能够应用 Spring 反对的 @ResponseBody、ResponseEntity。
- HandlerExceptionResolver 办法申明返回值类型只能是 ModelAndView,如果须要返回 JSON、xml 等须要本人实现.。
缓存利用
- @Controller+@ExceptionHandler 的缓存信息在 ExceptionHandlerExceptionResolver 的 exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler 的缓存信息在 ExceptionHandlerExceptionResolver 的 exceptionHandlerAdviceCache 中,
- HandlerExceptionResolver 接口是不做缓存的,在异样报错的状况下才会走本人的 HandlerExceptionResolver 实现类,多少有点性能损耗.