在项目中,经常会使用ExceptionHandler来作为全局性的异常处理中心。那么ExceptionHandler处理异常的原理是什么呢,今天就来分析一下。
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = RuntimeException.class) public String handle(){ return "error"; } } 复制代码
使用还是很简单的,在类上面添加ControllerAdvice注解,在方法上面添加ExceptionHandler注解,就可以在方法里处理相应的异常信息了。
异常处理的核心类是ExceptionHandlerExceptionResolver,进入该类。查看afterPropertiesSet方法。
public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); ... } private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } //这行代码会找出所有标记了ControllerAdvice注解的类 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } //遍历这些类,找出有ExceptionHandler注解标注的方法。 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); if (resolver.hasExceptionMappings()) { this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); } } ... } 复制代码
通过上述代码可以看出,在ExceptionHandlerExceptionResolver类中,该类扫描了所有标注有ExceptionHandler注解的方法,并将他们存入了exceptionHandlerAdviceCache中。
看过了ControllerAdvice和ExceptionHandler注解的作用后,我们来看一下异常处理的原理。进入DispatcherServlet的doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... //处理controller方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ... } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } //异常处理中心 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } ... } 复制代码
从doDispatch方法中可以看出,程序先处理了controller层的业务逻辑,对于业务逻辑抛出的异常,程序统一做了封装,然后进入了processDispatchResult方法中进行处理。所以我们进入该方法一探究竟。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; //如果程序发生了异常,就进行处理 if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } ... } protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { // Success and error responses may use different content types request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); // Check registered HandlerExceptionResolvers... ModelAndView exMv = null; if (this.handlerExceptionResolvers != null) { //遍历handlerExceptionResolvers处理异常信息 for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { exMv = resolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } } ... } 复制代码
那这边的handlerExceptionResolvers是哪里来的呢?
private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); ... } ... } 复制代码
在DispatcherServlet初始化的时候,会去容器中找HandlerExceptionResolver类型的类。而刚刚的ExceptionHandlerExceptionResolver类就是继承了HandlerExceptionResolver接口,所以这个地方就将他放入了DispatcherServlet中。所以上面的遍历handlerExceptionResolvers处理异常信息的地方,就是调用了ExceptionHandlerExceptionResolver的resolveException方法。所以我们进入该方法。
public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { if (shouldApplyTo(request, handler)) { prepareResponse(ex, response); ModelAndView result = doResolveException(request, response, handler, ex); ... } } protected final ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex); } protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); ... else { // Otherwise, just the given exception as-is exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); } } ... } public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ... } public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //获取方法的参数 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } //执行方法 return doInvoke(args); } 复制代码
整个异常的执行逻辑如上面的代码,简单点说就是找到相应的异常处理方法,执行他。这个地方getMethodArgumentValues里面的逻辑和 SpringBoot源码解析-controller层参数的封装 是一样的,但是他们能处理的参数类型却不一样。
查看ExceptionHandlerExceptionResolver类的afterPropertiesSet方法:
public void afterPropertiesSet() { ... if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } ... } protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); // Annotation-based argument resolution resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } return resolvers; } 复制代码
这边就是ExceptionHandler方法中可以接收的参数类型了。看一下,要比controller那边的类型少了许多,使用的时候注意一下即可。