• 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注解的解析,初始化完后的信息供后续解析异常使用。

    实现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.
    	 * 

    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 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注解异常类型最近的一个(深度最小的那个也即是继承关系最少的那个)具体代码如下:

    ExceptionHandlerMethodResolver
    public 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 Mapextends Throwable>, Method> mappedMethods = new HashMap<>(16);
    
    	/**
    	 * 缓存,用来存储先前碰到过的异常类型与处理方法的映射
    	 */
    	private final Mapextends 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 (Classextends 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 Listextends Throwable>> detectExceptionMappings(Method method) {
    		Listextends 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((Classextends Throwable>) paramType);
    				}
    			}
    		}
    		if (result.isEmpty()) {
    			throw new IllegalStateException("No exception types mapped to " + method);
    		}
    		return result;
    	}
    
    	protected void detectAnnotationExceptionMappings(Method method, List> 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 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 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 exceptionType) {
    		Listextends Throwable>> matches = new ArrayList<>();
    		for (Classextends 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;
    		}
    	}
    
    }
    @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 flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
            return mav;
        }
    }
    折叠

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

     

    2. demo

    Controller类方法:

    @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异常处理类

    @ControllerAdvice
    @ResponseBody
    public class ExceptionHandlerController {
    
      @ExceptionHandler(value = Exception.class)
      public Object handleException(Exception e) {
        return CommonResult.fail("Exception:" + e.getMessage());
      }
    
      @ExceptionHandler(value = RuntimeException.class)
      public Object handlerRuntimeException(Exception e) {
        return CommonResult.fail("handlerRuntimeException:" + e.getMessage());
      }
    }

    ExceptionHandlerController类中定义了两个异常处理方法,一个处理Exception异常,一个处理RuntimeException异常,那个根据controller方法抛出的异常RuntimeException再结合上面的分析(RuntimeException到RuntimeException深度为0,RuntimeException到Exception中间继承了一次深度为1)可以得出抛出异常类型的处理方法为handlerRuntimeException 方法。 运行程序结果如下:

     

    结语

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

  • 相关阅读:
    FPGA实现图像对比度自动调整,提供2套工程源码和技术支持
    Mybatis 日志(Apache Commons Logging)
    docker-compose:搭建酷炫私有云相册photoprism
    quarkus数据库篇之三:单应用同时操作多个数据库
    如何使用Net2FTP搭建免费web文件管理器打造个人网盘
    SpringCloud:前端调用接口时报The header contains multiple values ‘*, *‘, but only one
    JVM 上数据处理语言的竞争:Kotlin, Scala 和 SPL
    vue2升级vue3: TSX Vue 3 Composition API Refs
    vue-自适应布局-postcss-pxtorem
    Python Day9 字符串进阶【零基础】
  • 原文地址:https://www.cnblogs.com/monianxd/p/16526098.html