十念 发表于 2022-9-2 17:16:02

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
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                        @Nullable Exception exception) throws Exception {

                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);
                        }
                }

                // Did the handler return a view to render? 处理程序是否返回要渲染的视图
                if (mv != null && !mv.wasCleared()) {
                        // 渲染视图
                        render(mv, request, response);
                        if (errorView) {
                                WebUtils.clearErrorRequestAttributes(request);
                        }
                }
                else {
                        if (logger.isDebugEnabled()) {
                                logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                                                "': assuming HandlerAdapter completed request handling");
                        }
                }
        }        @Nullable
        protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
                        @Nullable Object handler, Exception ex) throws Exception {

                // Check registered HandlerExceptionResolvers...
                ModelAndView exMv = null;
                if (this.handlerExceptionResolvers != null) {
                        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
                                exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
                                if (exMv != null) {
                                        break;
                                }
                        }
                }
                if (exMv != null) {
                        // 无视图view
                        if (exMv.isEmpty()) {
                                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                                return null;
                        }
                        // We might still need view name translation for a plain error model...
                        if (!exMv.hasView()) {
                                String defaultViewName = getDefaultViewName(request);
                                if (defaultViewName != null) {
                                        exMv.setViewName(defaultViewName);
                                }
                        }
                        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
                        return exMv;
                }

                throw ex;
        } 
其中最重要也是最常使用的一个处理器就是ExceptionHandlerExceptionResolver,下面将着重介绍它,先来看看这个类的继承结构图,实现了InitializingBean接口,在这个bean创建完成之前会调用生命周期初始化方法afterPropertiesSet(),这里面包含了对@ControllerAdvice注解的解析,初始化完后的信息供后续解析异常使用。
https://img2022.cnblogs.com/blog/1504541/202207/1504541-20220727175706186-802090884.png
实现HandlerExceptionResolver接口,实现解析方法resolveException()
public interface HandlerExceptionResolver {

        /**
       * Try to resolve the given exception that got thrown during handler execution,
       * returning a {@link ModelAndView} that represents a specific error page if appropriate.
       * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
       * to indicate that the exception has been resolved successfully but that no view
       * should be rendered, for instance by setting a status code.
       * @param request current HTTP request
       * @param response current HTTP response
       * @param handler the executed handler, or {@code null} if none chosen at the
       * time of the exception (for example, if multipart resolution failed)
       * @param ex the exception that got thrown during handler execution
       * @return a corresponding {@code ModelAndView} to forward to,
       * or {@code null} for default processing in the resolution chain
       */
        @Nullable
        ModelAndView resolveException(
                        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    // 初始化异常注解 @ControllerAdvice
    initExceptionHandlerAdviceCache();
}

private void initExceptionHandlerAdviceCache() {
    if (getApplicationContext() == null) {
      return;
    }
    if (logger.isDebugEnabled()) {
      logger.debug("Looking for exception mappings: " + getApplicationContext());
    }

    // 解析有@ControllerAdvice注解的bean,并将这个bean构建成ControllerAdviceBean对象
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    // 将ControllerAdviceBean根据order排序
    AnnotationAwareOrderComparator.sort(adviceBeans);

    for (ControllerAdviceBean adviceBean : adviceBeans) {
      Class<?> beanType = adviceBean.getBeanType();
      if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
      }
      ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
      // mappedMethods 映射不为空
      if (resolver.hasExceptionMappings()) {
            // 添加到缓存中
            this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            if (logger.isInfoEnabled()) {
                logger.info("Detected @ExceptionHandler methods in " + adviceBean);
            }
      }
      // 若实现了ResponseBodyAdvice接口(暂不介绍)
      if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            this.responseBodyAdvice.add(adviceBean);
            if (logger.isInfoEnabled()) {
                logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
            }
      }
    }
} ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); 这行代码会解析拥有@ControllerAdvice 注解的class,并且会遍历class中带有 @ExceptionHandler 注解的方法,获取方法注解带有的异常类型,将异常类型和方法放入到mappedMethods中供后面获取,获取的时候若对应处理此异常类型的method有多个,则需要进行排序,选取一个异常类型与method ExceptionHandler注解异常类型最近的一个(深度最小的那个也即是继承关系最少的那个)具体代码如下:
ExceptionHandlerMethodResolverpublic class ExceptionHandlerMethodResolver {

        /**
       * A filter for selecting {@code @ExceptionHandler} methods.
       */
        public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
                        (AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null);


        /**
       * 异常类型与方法的映射map
       */
        private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);

        /**
       * 缓存,用来存储先前碰到过的异常类型与处理方法的映射
       */
        private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);


        /**
       * A constructor that finds {@link ExceptionHandler} methods in the given type.
       * @param handlerType the type to introspect
       */
        public ExceptionHandlerMethodResolver(Class<?> handlerType) {
                // 获取并遍历@ExceptionHandler注解的方法
                for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
                        for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
                                addExceptionMapping(exceptionType, method);
                        }
                }
        }


        /**
       * Extract exception mappings from the {@code @ExceptionHandler} annotation first,
       * and then as a fallback from the method signature itself.
       */
        @SuppressWarnings("unchecked")
        private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
                List<Class<? extends Throwable>> result = new ArrayList<>();
                // 将注解ExceptionHandler value值异常添加到result中
                detectAnnotationExceptionMappings(method, result);
                // 注解值为空的话再去获取参数的异常类型
                if (result.isEmpty()) {
                        for (Class<?> paramType : method.getParameterTypes()) {
                                if (Throwable.class.isAssignableFrom(paramType)) {
                                        result.add((Class<? extends Throwable>) paramType);
                                }
                        }
                }
                if (result.isEmpty()) {
                        throw new IllegalStateException("No exception types mapped to " + method);
                }
                return result;
        }

        protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
                ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
                Assert.state(ann != null, "No ExceptionHandler annotation");
                result.addAll(Arrays.asList(ann.value()));
        }

        private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
                // 将异常类型以及对应的method添加到map中,且异常类型不能有重复否则会报错
                Method oldMethod = this.mappedMethods.put(exceptionType, method);
                if (oldMethod != null && !oldMethod.equals(method)) {
                        throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
                                        exceptionType + "]: {" + oldMethod + ", " + method + "}");
                }
        }

        /**
       * Whether the contained type has any exception mappings.
       */
        public boolean hasExceptionMappings() {
                return !this.mappedMethods.isEmpty();
        }

        /**
       * Find a {@link Method} to handle the given exception.
       * Use {@link ExceptionDepthComparator} if more than one match is found.
       * @param exception the exception
       * @return a Method to handle the exception, or {@code null} if none found
       */
        @Nullable
        public Method resolveMethod(Exception exception) {
                return resolveMethodByThrowable(exception);
        }

        /**
       * Find a {@link Method} to handle the given Throwable.
       * Use {@link ExceptionDepthComparator} if more than one match is found.
       * @param exception the exception
       * @return a Method to handle the exception, or {@code null} if none found
       * @since 5.0
       */
        @Nullable
        public Method resolveMethodByThrowable(Throwable exception) {
                Method method = resolveMethodByExceptionType(exception.getClass());
                if (method == null) {
                        Throwable cause = exception.getCause();
                        if (cause != null) {
                                method = resolveMethodByExceptionType(cause.getClass());
                        }
                }
                return method;
        }

        /**
       * Find a {@link Method} to handle the given exception type. This can be
       * useful if an {@link Exception} instance is not available (e.g. for tools).
       * @param exceptionType the exception type
       * @return a Method to handle the exception, or {@code null} if none found
       */
        @Nullable
        public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
                Method method = this.exceptionLookupCache.get(exceptionType);
                if (method == null) {
                        method = getMappedMethod(exceptionType);
                        this.exceptionLookupCache.put(exceptionType, method);
                }
                return method;
        }

        /**
       * Return the {@link Method} mapped to the given exception type, or {@code null} if none.
       */
        @Nullable
        private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
                List<Class<? extends Throwable>> matches = new ArrayList<>();
                for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
                        if (mappedException.isAssignableFrom(exceptionType)) {
                                matches.add(mappedException);
                        }
                }
                if (!matches.isEmpty()) {
                        // exceptionType 到matchs父类异常类型的深度
                        matches.sort(new ExceptionDepthComparator(exceptionType));
                        return this.mappedMethods.get(matches.get(0));
                }
                else {
                        return null;
                }
        }

}exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); 此方法执行完成后已经完成了异常处理方法的调用,若方法返回值为视图ModelAndView或其他视图类型,则还需要借助视图解析器如InternalResourceViewResolver对视图进行解析渲染,若为其他类型的值则将值写入到response响应中。
 
2. demo

Controller类方法:
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
                                                       HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

    // exception为controller方法抛出的异常
    // 根据异常及其类型从上述的mappedMethods中获取对应的方法,再获取方法所在的对象 封装成ServletInvocableHandlerMethod
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
    if (exceptionHandlerMethod == null) {
      return null;
    }

    // 设置参数解析器,主要用来获取方法的参数值的,供后续反射调用方法
    if (this.argumentResolvers != null) {
      exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    // 设置返回值解析器,当执行完方法后获取返回值,对返回值进行处理 或返回视图或将结果写入到response
    if (this.returnValueHandlers != null) {
      exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();

    try {
      if (logger.isDebugEnabled()) {
            logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
      }
      Throwable cause = exception.getCause();
      if (cause != null) {
            // Expose cause as provided argument as well
            // 执行异常处理方法,也就是我们的自定义的异常处理方法
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
      }
      else {
            // Otherwise, just the given exception as-is
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
      }
    }
    catch (Throwable invocationEx) {
      // Any other than the original exception is unintended here,
      // probably an accident (e.g. failed assertion or the like).
      if (invocationEx != exception && logger.isWarnEnabled()) {
            logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
      }
      // Continue with default processing of the original exception...
      return null;
    }

    // 根据后续的返回值解析器设置的,将返回值写入到response中了直接返回空的mav
    if (mavContainer.isRequestHandled()) {
      return new ModelAndView();
    }
    else {
      ModelMap model = mavContainer.getModel();
      HttpStatus status = mavContainer.getStatus();
      ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
      mav.setViewName(mavContainer.getViewName());
      // (this.view instanceof String)
      if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
      }
      if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
      }
      return mav;
    }
}ExceptionHandlerController异常处理类
@Controller
@RequestMapping(value = "test")
public class HelloWorldController{

@Data
public static class User {
    private String username;

    private Integer age;

    private String address;
}


@RequestMapping(value = "user/get", method = RequestMethod.POST)
@ResponseBody
public Object testObject(@RequestBody @Valid User user, @RequestParam String address) {
    user.setAddress(address);
    // 这里特意抛出RuntimeException异常
    throw new RuntimeException("this is a exception");
}

}ExceptionHandlerController类中定义了两个异常处理方法,一个处理Exception异常,一个处理RuntimeException异常,那个根据controller方法抛出的异常RuntimeException再结合上面的分析(RuntimeException到RuntimeException深度为0,RuntimeException到Exception中间继承了一次深度为1)可以得出抛出异常类型的处理方法为handlerRuntimeException 方法。 运行程序结果如下:
https://img2022.cnblogs.com/blog/1504541/202207/1504541-20220727194354930-1321616917.png
 
结语

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: springmvc异常处理解析#ExceptionHandlerExceptionResolver