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方法。
在上面的@ExceptionHandler方法中,我们通过ResponseEntity将异常信息提供给客户端,HTTP状态码设置为500。这使客户端了解已发生错误,并能够在日志中记录异常信息以便日后调试。
总之,使用ExceptionHandler能够更好的掌控应用的异常信息,使得应用在发生异常的时候更加可控,并且更加容易进行调试。
ExceptionHandler的注意事项
-
@ExceptionHandler下方法返回值类型支持多种,常见的ModelAndView,@ResponseBody注解标注,ResponseEntity等类型都OK。
Controller类下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常。
源码分析介绍
原理说明-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
HandlerExceptionResolver.ExceptionHandlerExceptionResolver, 继承关系如下面所示。
原理说明-AbstractHandlerExceptionResolver
这里AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用来记录日志、prepareResponse方法,用来设置response的Cache-Control。
异常处理方法就位于doResolveException
原理说明-AbstractHandlerMethodExceptionResolver
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo
父类AbstractHandlerExceptionResolver的shouldApplyTo方法.
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#shouldApplyTo
doResolveException
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
Spring请求方法执行一样的处理方式,设置argumentResolvers、returnValueHandlers,之后进行调用异常处理方法。
获取@ExceptionHandler
@ExceptionHandler方法返回值常见的可以是: ModelAndView 、@ResponseBody注解、ResponseEntity。
getExceptionHandlerMethod方法
exceptionHandlerCache是针对Controller层面的@ExceptionHandler的处理方式,而exceptionHandlerAdviceCache是针对@ControllerAdvice的处理方式. 这两个属性都位于ExceptionHandlerExceptionResolver中。
resolveMethod方法
resolveMethodByExceptionType方法,尝试从缓存A:exceptionLookupCache中根据异常class类型获取Method,初始时候肯定缓存为空,就去遍历ExceptionHandlerMethodResolver的mappedMethods(上面提及了key为异常类型,value为method,exceptionType为当前@RequestMapping方法抛出的异常,判断当前异常类型是不是@ExceptionHandler中value声明的子类或本身,满足条件就代表匹配上了;
全局级别异常处理器实现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,可能异常就没有被捕获。
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初始化逻辑:
Spring父子容器中所有@ControllerAdivce的bean的方法
代码片段位于:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans
比较说明
@Controller+@ExceptionHandler、HandlerExceptionResolver接口形式、@ControllerAdvice+@ExceptionHandler优缺点说明:
调用优先级
-
@Controller+@ExceptionHandler优先级最高
- @ControllerAdvice+@ExceptionHandler 略低
- HandlerExceptionResolver最低。
三种方式并存的情况 优先级越高的越先选择,而且被一个捕获处理了就不去执行其他的。
三种方式都支持多种返回类型
-
HandlerExceptionResolver方法声明返回值类型只能是 ModelAndView,如果需要返回JSON、xml等需要自己实现.。
缓存利用
-
HandlerExceptionResolver接口是不做缓存的,在异常报错的情况下才会走自己的HandlerExceptionResolver实现类,多少有点性能损耗。
编程笔记 » [Spring专题]「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理