ToB企服应用市场:ToB评测及商务社交产业平台

标题: springmvc异常处理解析#ExceptionHandlerExceptionResolver [打印本页]

作者: 忿忿的泥巴坨    时间: 2022-9-2 17:16
标题: springmvc异常处理解析#ExceptionHandlerExceptionResolver
开头

试想一下我们一般怎么统一处理异常呢,答:切面。但抛开切面不讲,如果对每一个controller方法抛出的异常做专门处理,那么着实太费劲了,有没有更好的方法呢?当然有,就是本篇文章接下来要介绍的springmvc的异常处理机制,用到了ControllerAdvice和ExceptionHandler注解,有点切面的感觉哈哈。
 
1.ExceptionHandlerExceptionResolver

首先从springmvc的异常处理解析器开始讲,当执行完controller方法后,不管有没有异常产生都会调用DispatcherServlet#doDispatch()方法中的processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 方法,接着会判断是否有异常,若无异常则走正常流程,若有异常则需要进行处理 mv = processHandlerException(request, response, handler, exception);  再接着就是遍历spring已经注册的异常处理解析器直到有处理器返回mav
  1. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  2.                         @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
  3.                         @Nullable Exception exception) throws Exception {
  4.                 if (exception != null) {
  5.                         if (exception instanceof ModelAndViewDefiningException) {
  6.                                 logger.debug("ModelAndViewDefiningException encountered", exception);
  7.                                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
  8.                         }
  9.                         else {
  10.                                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
  11.                                 // 执行处理器产生的异常处理
  12.                                 mv = processHandlerException(request, response, handler, exception);
  13.                                 // 是否有异常视图返回
  14.                                 errorView = (mv != null);
  15.                         }
  16.                 }
  17.                 // Did the handler return a view to render? 处理程序是否返回要渲染的视图
  18.                 if (mv != null && !mv.wasCleared()) {
  19.                         // 渲染视图
  20.                         render(mv, request, response);
  21.                         if (errorView) {
  22.                                 WebUtils.clearErrorRequestAttributes(request);
  23.                         }
  24.                 }
  25.                 else {
  26.                         if (logger.isDebugEnabled()) {
  27.                                 logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
  28.                                                 "': assuming HandlerAdapter completed request handling");
  29.                         }
  30.                 }
  31.         }
复制代码
  1.         @Nullable
  2.         protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
  3.                         @Nullable Object handler, Exception ex) throws Exception {
  4.                 // Check registered HandlerExceptionResolvers...
  5.                 ModelAndView exMv = null;
  6.                 if (this.handlerExceptionResolvers != null) {
  7.                         for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
  8.                                 exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
  9.                                 if (exMv != null) {
  10.                                         break;
  11.                                 }
  12.                         }
  13.                 }
  14.                 if (exMv != null) {
  15.                         // 无视图view
  16.                         if (exMv.isEmpty()) {
  17.                                 request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
  18.                                 return null;
  19.                         }
  20.                         // We might still need view name translation for a plain error model...
  21.                         if (!exMv.hasView()) {
  22.                                 String defaultViewName = getDefaultViewName(request);
  23.                                 if (defaultViewName != null) {
  24.                                         exMv.setViewName(defaultViewName);
  25.                                 }
  26.                         }
  27.                         WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
  28.                         return exMv;
  29.                 }
  30.                 throw ex;
  31.         }
复制代码
 
其中最重要也是最常使用的一个处理器就是ExceptionHandlerExceptionResolver,下面将着重介绍它,先来看看这个类的继承结构图,实现了InitializingBean接口,在这个bean创建完成之前会调用生命周期初始化方法afterPropertiesSet(),这里面包含了对@ControllerAdvice注解的解析,初始化完后的信息供后续解析异常使用。

实现HandlerExceptionResolver接口,实现解析方法resolveException()
  1. public interface HandlerExceptionResolver {
  2.         /**
  3.          * Try to resolve the given exception that got thrown during handler execution,
  4.          * returning a {@link ModelAndView} that represents a specific error page if appropriate.
  5.          * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
  6.          * to indicate that the exception has been resolved successfully but that no view
  7.          * should be rendered, for instance by setting a status code.
  8.          * @param request current HTTP request
  9.          * @param response current HTTP response
  10.          * @param handler the executed handler, or {@code null} if none chosen at the
  11.          * time of the exception (for example, if multipart resolution failed)
  12.          * @param ex the exception that got thrown during handler execution
  13.          * @return a corresponding {@code ModelAndView} to forward to,
  14.          * or {@code null} for default processing in the resolution chain
  15.          */
  16.         @Nullable
  17.         ModelAndView resolveException(
  18.                         HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
  19. }
复制代码
  1. @Override
  2. public void afterPropertiesSet() {
  3.     // Do this first, it may add ResponseBodyAdvice beans
  4.     // 初始化异常注解 @ControllerAdvice
  5.     initExceptionHandlerAdviceCache();
  6. }
  7. private void initExceptionHandlerAdviceCache() {
  8.     if (getApplicationContext() == null) {
  9.         return;
  10.     }
  11.     if (logger.isDebugEnabled()) {
  12.         logger.debug("Looking for exception mappings: " + getApplicationContext());
  13.     }
  14.     // 解析有@ControllerAdvice注解的bean,并将这个bean构建成ControllerAdviceBean对象
  15.     List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
  16.     // 将ControllerAdviceBean根据order排序
  17.     AnnotationAwareOrderComparator.sort(adviceBeans);
  18.     for (ControllerAdviceBean adviceBean : adviceBeans) {
  19.         Class<?> beanType = adviceBean.getBeanType();
  20.         if (beanType == null) {
  21.             throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
  22.         }
  23.         ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
  24.         // mappedMethods 映射不为空
  25.         if (resolver.hasExceptionMappings()) {
  26.             // 添加到缓存中
  27.             this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
  28.             if (logger.isInfoEnabled()) {
  29.                 logger.info("Detected @ExceptionHandler methods in " + adviceBean);
  30.             }
  31.         }
  32.         // 若实现了ResponseBodyAdvice接口(暂不介绍)
  33.         if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
  34.             this.responseBodyAdvice.add(adviceBean);
  35.             if (logger.isInfoEnabled()) {
  36.                 logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
  37.             }
  38.         }
  39.     }
  40. }
复制代码
 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); 这行代码会解析拥有@ControllerAdvice 注解的class,并且会遍历class中带有 @ExceptionHandler 注解的方法,获取方法注解带有的异常类型,将异常类型和方法放入到mappedMethods中供后面获取,获取的时候若对应处理此异常类型的method有多个,则需要进行排序,选取一个异常类型与method ExceptionHandler注解异常类型最近的一个(深度最小的那个也即是继承关系最少的那个)具体代码如下:
ExceptionHandlerMethodResolver
  1. public class ExceptionHandlerMethodResolver {
  2.         /**
  3.          * A filter for selecting {@code @ExceptionHandler} methods.
  4.          */
  5.         public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
  6.                         (AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null);
  7.         /**
  8.          * 异常类型与方法的映射map
  9.          */
  10.         private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
  11.         /**
  12.          * 缓存,用来存储先前碰到过的异常类型与处理方法的映射
  13.          */
  14.         private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
  15.         /**
  16.          * A constructor that finds {@link ExceptionHandler} methods in the given type.
  17.          * @param handlerType the type to introspect
  18.          */
  19.         public ExceptionHandlerMethodResolver(Class<?> handlerType) {
  20.                 // 获取并遍历@ExceptionHandler注解的方法
  21.                 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
  22.                         for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
  23.                                 addExceptionMapping(exceptionType, method);
  24.                         }
  25.                 }
  26.         }
  27.         /**
  28.          * Extract exception mappings from the {@code @ExceptionHandler} annotation first,
  29.          * and then as a fallback from the method signature itself.
  30.          */
  31.         @SuppressWarnings("unchecked")
  32.         private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
  33.                 List<Class<? extends Throwable>> result = new ArrayList<>();
  34.                 // 将注解ExceptionHandler value值异常添加到result中
  35.                 detectAnnotationExceptionMappings(method, result);
  36.                 // 注解值为空的话再去获取参数的异常类型
  37.                 if (result.isEmpty()) {
  38.                         for (Class<?> paramType : method.getParameterTypes()) {
  39.                                 if (Throwable.class.isAssignableFrom(paramType)) {
  40.                                         result.add((Class<? extends Throwable>) paramType);
  41.                                 }
  42.                         }
  43.                 }
  44.                 if (result.isEmpty()) {
  45.                         throw new IllegalStateException("No exception types mapped to " + method);
  46.                 }
  47.                 return result;
  48.         }
  49.         protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
  50.                 ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
  51.                 Assert.state(ann != null, "No ExceptionHandler annotation");
  52.                 result.addAll(Arrays.asList(ann.value()));
  53.         }
  54.         private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
  55.                 // 将异常类型以及对应的method添加到map中,且异常类型不能有重复否则会报错
  56.                 Method oldMethod = this.mappedMethods.put(exceptionType, method);
  57.                 if (oldMethod != null && !oldMethod.equals(method)) {
  58.                         throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
  59.                                         exceptionType + "]: {" + oldMethod + ", " + method + "}");
  60.                 }
  61.         }
  62.         /**
  63.          * Whether the contained type has any exception mappings.
  64.          */
  65.         public boolean hasExceptionMappings() {
  66.                 return !this.mappedMethods.isEmpty();
  67.         }
  68.         /**
  69.          * Find a {@link Method} to handle the given exception.
  70.          * Use {@link ExceptionDepthComparator} if more than one match is found.
  71.          * @param exception the exception
  72.          * @return a Method to handle the exception, or {@code null} if none found
  73.          */
  74.         @Nullable
  75.         public Method resolveMethod(Exception exception) {
  76.                 return resolveMethodByThrowable(exception);
  77.         }
  78.         /**
  79.          * Find a {@link Method} to handle the given Throwable.
  80.          * Use {@link ExceptionDepthComparator} if more than one match is found.
  81.          * @param exception the exception
  82.          * @return a Method to handle the exception, or {@code null} if none found
  83.          * @since 5.0
  84.          */
  85.         @Nullable
  86.         public Method resolveMethodByThrowable(Throwable exception) {
  87.                 Method method = resolveMethodByExceptionType(exception.getClass());
  88.                 if (method == null) {
  89.                         Throwable cause = exception.getCause();
  90.                         if (cause != null) {
  91.                                 method = resolveMethodByExceptionType(cause.getClass());
  92.                         }
  93.                 }
  94.                 return method;
  95.         }
  96.         /**
  97.          * Find a {@link Method} to handle the given exception type. This can be
  98.          * useful if an {@link Exception} instance is not available (e.g. for tools).
  99.          * @param exceptionType the exception type
  100.          * @return a Method to handle the exception, or {@code null} if none found
  101.          */
  102.         @Nullable
  103.         public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
  104.                 Method method = this.exceptionLookupCache.get(exceptionType);
  105.                 if (method == null) {
  106.                         method = getMappedMethod(exceptionType);
  107.                         this.exceptionLookupCache.put(exceptionType, method);
  108.                 }
  109.                 return method;
  110.         }
  111.         /**
  112.          * Return the {@link Method} mapped to the given exception type, or {@code null} if none.
  113.          */
  114.         @Nullable
  115.         private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
  116.                 List<Class<? extends Throwable>> matches = new ArrayList<>();
  117.                 for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
  118.                         if (mappedException.isAssignableFrom(exceptionType)) {
  119.                                 matches.add(mappedException);
  120.                         }
  121.                 }
  122.                 if (!matches.isEmpty()) {
  123.                         // exceptionType 到matchs父类异常类型的深度
  124.                         matches.sort(new ExceptionDepthComparator(exceptionType));
  125.                         return this.mappedMethods.get(matches.get(0));
  126.                 }
  127.                 else {
  128.                         return null;
  129.                 }
  130.         }
  131. }
复制代码
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); 此方法执行完成后已经完成了异常处理方法的调用,若方法返回值为视图ModelAndView或其他视图类型,则还需要借助视图解析器如InternalResourceViewResolver对视图进行解析渲染,若为其他类型的值则将值写入到response响应中。
 
2. demo

Controller类方法:
  1. @Override
  2. @Nullable
  3. protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
  4.                                                        HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
  5.     // exception为controller方法抛出的异常
  6.     // 根据异常及其类型从上述的mappedMethods中获取对应的方法,再获取方法所在的对象 封装成ServletInvocableHandlerMethod
  7.     ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
  8.     if (exceptionHandlerMethod == null) {
  9.         return null;
  10.     }
  11.     // 设置参数解析器,主要用来获取方法的参数值的,供后续反射调用方法
  12.     if (this.argumentResolvers != null) {
  13.         exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
  14.     }
  15.     // 设置返回值解析器,当执行完方法后获取返回值,对返回值进行处理 或返回视图或将结果写入到response
  16.     if (this.returnValueHandlers != null) {
  17.         exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
  18.     }
  19.     ServletWebRequest webRequest = new ServletWebRequest(request, response);
  20.     ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  21.     try {
  22.         if (logger.isDebugEnabled()) {
  23.             logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
  24.         }
  25.         Throwable cause = exception.getCause();
  26.         if (cause != null) {
  27.             // Expose cause as provided argument as well
  28.             // 执行异常处理方法,也就是我们的自定义的异常处理方法
  29.             exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
  30.         }
  31.         else {
  32.             // Otherwise, just the given exception as-is
  33.             exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
  34.         }
  35.     }
  36.     catch (Throwable invocationEx) {
  37.         // Any other than the original exception is unintended here,
  38.         // probably an accident (e.g. failed assertion or the like).
  39.         if (invocationEx != exception && logger.isWarnEnabled()) {
  40.             logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
  41.         }
  42.         // Continue with default processing of the original exception...
  43.         return null;
  44.     }
  45.     // 根据后续的返回值解析器设置的,将返回值写入到response中了直接返回空的mav
  46.     if (mavContainer.isRequestHandled()) {
  47.         return new ModelAndView();
  48.     }
  49.     else {
  50.         ModelMap model = mavContainer.getModel();
  51.         HttpStatus status = mavContainer.getStatus();
  52.         ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
  53.         mav.setViewName(mavContainer.getViewName());
  54.         // (this.view instanceof String)
  55.         if (!mavContainer.isViewReference()) {
  56.             mav.setView((View) mavContainer.getView());
  57.         }
  58.         if (model instanceof RedirectAttributes) {
  59.             Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
  60.             RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
  61.         }
  62.         return mav;
  63.     }
  64. }
复制代码
ExceptionHandlerController异常处理类
  1. @Controller
  2. @RequestMapping(value = "test")
  3. public class HelloWorldController{
  4.   @Data
  5.   public static class User {
  6.     private String username;
  7.     private Integer age;
  8.     private String address;
  9.   }
  10.   @RequestMapping(value = "user/get", method = RequestMethod.POST)
  11.   @ResponseBody
  12.   public Object testObject(@RequestBody @Valid User user, @RequestParam String address) {
  13.     user.setAddress(address);
  14.     // 这里特意抛出RuntimeException异常
  15.     throw new RuntimeException("this is a exception");
  16.   }
  17. }
复制代码
ExceptionHandlerController类中定义了两个异常处理方法,一个处理Exception异常,一个处理RuntimeException异常,那个根据controller方法抛出的异常RuntimeException再结合上面的分析(RuntimeException到RuntimeException深度为0,RuntimeException到Exception中间继承了一次深度为1)可以得出抛出异常类型的处理方法为handlerRuntimeException 方法。 运行程序结果如下:

 
结语

初步解析ExceptionHandlerExceptionResolver源码,若写的有误或者有不理解的地方,欢迎指出讨论~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4