Spring MVC : 工具 ExceptionHandlerExceptionResolver

深入探讨SpringMVC中ExceptionHandlerExceptionResolver如何处理@Controller组件@RequestMapping方法中的异常,包括其工作流程、内部实现细节及配置选项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

Spring MVC内置实现的HandlerExceptionResolver,仅仅针对Handler类型为HandlerMethod中发生异常的情况,也就是@Controller组件类中的@RequestMapping方法中的异常。当这类异常交给ExceptionHandlerExceptionResolver解析时,它会首先查看发生异常的控制器方法所在的控制器类中是否有合适的@ExceptionHanlder方法,然后看所有的@ControllerAdvice类中是否有合适的@ExceptionHanlder方法,如果有,ExceptionHandlerExceptionResolver就会使用相应的方法处理该异常。该方法对异常的处理过程很类似一个控制器方法对用户请求的处理:Spring MVC : 控制器方法处理请求的过程分析 - 0. 概述,结果也主要有两种大的分支 :

  1. 将请求处理完全,调用者后续无需继续处理请求;
  2. 返回一个ModelAndView对象,调用者后续需要基于该对象继续视图解析和渲染;

    ModelAndView对象为空对象(#isEmpty返回true)表示要调用者使用缺省解析机制。

ExceptionHandlerExceptionResolver 的使用位置 :

    // DispatcherServlet 代码片段
	@Nullable
	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) { 
           // 缺省情况下 this.handlerExceptionResolvers 中会有一个 
           // ExceptionHandlerExceptionResolver 对象
           // 而 this.handlerExceptionResolvers 会在 DispatcherServlet 初始化时从容器中扫描 
           // HandlerExceptionResolver 类型 bean 得到,而这些 bean 是开发人员或者框架的其他
           // 部分通过配置机制定义到容器的
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		// 省略无关代码
		// ...
	}

源代码

源代码版本 : spring-webmvc-5.1.5.RELEASE

package org.springframework.web.servlet.mvc.method.annotation;

// 省略 import 行

/**
 * An {@link AbstractHandlerMethodExceptionResolver} that resolves exceptions
 * through {@code @ExceptionHandler} methods.
 *
 * <p>Support for custom argument and return value types can be added via
 * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
 * Or alternatively to re-configure all argument and return value types use
 * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
 *
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @since 3.1
 */
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
		implements ApplicationContextAware, InitializingBean {

	@Nullable
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;

	@Nullable
	private HandlerMethodArgumentResolverComposite argumentResolvers;

	@Nullable
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

	@Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

    // 一组 HttpMessageConverter
    // 供能够直接输出响应体的 HandlerMethodReturnValueHandler 使用
	private List<HttpMessageConverter<?>> messageConverters;

	private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

    //  将异常处理控制器方法返回值转换成响应体时的Advice逻辑,可以做一些修饰,变形转换等等
	private final List<Object> responseBodyAdvice = new ArrayList<>();

	@Nullable
	private ApplicationContext applicationContext;

    // 保存发生异常的控制器方法所在的控制器组件中的@ExceptionHandler异常处理控制器方法
	private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
			new ConcurrentHashMap<>(64);

    // 缓存@ControllerAdvice bean组件中的@ExceptionHandler异常处理控制器方法
	private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> 
			exceptionHandlerAdviceCache =	new LinkedHashMap<>();


	public ExceptionHandlerExceptionResolver() {
       // 此构造函数方法体主要是初始化属性 this.messageConverters, 注意其初始值 :
       // 1. ByteArrayHttpMessageConverter 二进制字节流
       // 2. StringHttpMessageConverter, ISO8859-1 ,字符串
       // 3. SourceHttpMessageConverter , DOMSource/SAXSource/StAXSource/StreamSource等
       // 4. AllEncompassingFormHttpMessageConverter ,UTF-8, 用于读写表单数据和写multipart数据,
       // 在FormHttpMessageConverter上扩展,增加了 XML/JSON转换能力
		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
		stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

		this.messageConverters = new ArrayList<>();
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(stringHttpMessageConverter);
		try {
			this.messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Error err) {
			// Ignore when no TransformerFactory implementation is available
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}


	/**
	 * Provide resolvers for custom argument types. Custom resolvers are ordered
	 * after built-in ones. To override the built-in support for argument
	 * resolution use {@link #setArgumentResolvers} instead.
	 */
	public void setCustomArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> 
		argumentResolvers) {
		this.customArgumentResolvers= argumentResolvers;
	}

	/**
	 * Return the custom argument resolvers, or {@code null}.
	 */
	@Nullable
	public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
		return this.customArgumentResolvers;
	}

	/**
	 * Configure the complete list of supported argument types thus overriding
	 * the resolvers that would otherwise be configured by default.
	 */
	public void setArgumentResolvers(
			@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {
		if (argumentResolvers == null) {
			this.argumentResolvers = null;
		}
		else {
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
			this.argumentResolvers.addResolvers(argumentResolvers);
		}
	}

	/**
	 * Return the configured argument resolvers, or possibly {@code null} if
	 * not initialized yet via {@link #afterPropertiesSet()}.
	 */
	@Nullable
	public HandlerMethodArgumentResolverComposite getArgumentResolvers() {
		return this.argumentResolvers;
	}

	/**
	 * Provide handlers for custom return value types. Custom handlers are
	 * ordered after built-in ones. To override the built-in support for
	 * return value handling use {@link #setReturnValueHandlers}.
	 */
	public void setCustomReturnValueHandlers(
		@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		this.customReturnValueHandlers = returnValueHandlers;
	}

	/**
	 * Return the custom return value handlers, or {@code null}.
	 */
	@Nullable
	public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() {
		return this.customReturnValueHandlers;
	}

	/**
	 * Configure the complete list of supported return value types thus
	 * overriding handlers that would otherwise be configured by default.
	 */
	public void setReturnValueHandlers(
		@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		if (returnValueHandlers == null) {
			this.returnValueHandlers = null;
		}
		else {
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
			this.returnValueHandlers.addHandlers(returnValueHandlers);
		}
	}

	/**
	 * Return the configured handlers, or possibly {@code null} if not
	 * initialized yet via {@link #afterPropertiesSet()}.
	 */
	@Nullable
	public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
		return this.returnValueHandlers;
	}

	/**
	 * Set the message body converters to use.
	 * <p>These converters are used to convert from and to HTTP requests and responses.
	 */
	public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
		this.messageConverters = messageConverters;
	}

	/**
	 * Return the configured message body converters.
	 */
	public List<HttpMessageConverter<?>> getMessageConverters() {
		return this.messageConverters;
	}

	/**
	 * Set the {@link ContentNegotiationManager} to use to determine requested media types.
	 * If not set, the default constructor is used.
	 */
	public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
		this.contentNegotiationManager = contentNegotiationManager;
	}

	/**
	 * Return the configured {@link ContentNegotiationManager}.
	 */
	public ContentNegotiationManager getContentNegotiationManager() {
		return this.contentNegotiationManager;
	}

	/**
	 * Add one or more components to be invoked after the execution of a controller
	 * method annotated with {@code @ResponseBody} or returning {@code ResponseEntity}
	 * but before the body is written to the response with the selected
	 * {@code HttpMessageConverter}.
	 */
	public void setResponseBodyAdvice(@Nullable List<ResponseBodyAdvice<?>> responseBodyAdvice) {
		this.responseBodyAdvice.clear();
		if (responseBodyAdvice != null) {
			this.responseBodyAdvice.addAll(responseBodyAdvice);
		}
	}

	@Override
	public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	@Nullable
	public ApplicationContext getApplicationContext() {
		return this.applicationContext;
	}


   // InitializingBean 接口定义的初始化方法
	@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBodyAdvice beans
		initExceptionHandlerAdviceCache();

		if (this.argumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = 
				new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = 
				new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

	private void initExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}

       // 从容器中找到所有使用了注解 @ControllerAdvice 的 bean 组件并排序 
		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);
			}
           //  ExceptionHandlerMethodResolver 是一个工具类,它能够分析一个类,
           // 获取其中的注解 @ExceptionHandler 注解信息,建立 异常 和 异常处理控制器方法的 映射表
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
			if (resolver.hasExceptionMappings()) {
              //  resolver 分析完 beanType,发现其中存在使用@ExceptionHandler注解的方法,
              // 现在将其记录到 this.exceptionHandlerAdviceCache
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
			}
            
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
              // 当前 注解 @ControllerAdvice 的 bean 组件实现了接口 ResponseBodyAdvice ,
              // 现在将其记录到 this.responseBodyAdvice
				this.responseBodyAdvice.add(adviceBean);
			}
		}

		if (logger.isDebugEnabled()) {
			int handlerSize = this.exceptionHandlerAdviceCache.size();
			int adviceSize = this.responseBodyAdvice.size();
			if (handlerSize == 0 && adviceSize == 0) {
				logger.debug("ControllerAdvice beans: none");
			}
			else {
				logger.debug("ControllerAdvice beans: " +
						handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
			}
		}
	}

	/**
	 * Return an unmodifiable Map with the {@link ControllerAdvice @ControllerAdvice}
	 * beans discovered in the ApplicationContext. The returned map will be empty if
	 * the method is invoked before the bean has been initialized via
	 * {@link #afterPropertiesSet()}.
	 */
	public Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> 
		getExceptionHandlerAdviceCache() {
		return Collections.unmodifiableMap(this.exceptionHandlerAdviceCache);
	}

	/**
	 * 这里返回一个 HandlerMethodArgumentResolver 列表,是缺省情况下使用的异常处理
     * 控制器方法参数解析器
     * Return the list of argument resolvers to use including built-in resolvers
	 * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
	 */
	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;
	}

	/**
	 *  返回一个 HandlerMethodReturnValueHandler 列表
     * 用来处理异常处理控制器方法的返回值
     * Return the list of return value handlers to use including built-in and
	 * custom handlers provided via {@link #setReturnValueHandlers}.
	 */
	protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

		// Single-purpose return value types
		handlers.add(new ModelAndViewMethodReturnValueHandler());
		handlers.add(new ModelMethodProcessor());
		handlers.add(new ViewMethodReturnValueHandler());
		handlers.add(new HttpEntityMethodProcessor(
				getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));

		// Annotation-based return value types
		handlers.add(new ModelAttributeMethodProcessor(false));
		handlers.add(new RequestResponseBodyMethodProcessor(
				getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));

		// Multi-purpose return value types
		handlers.add(new ViewNameMethodReturnValueHandler());
		handlers.add(new MapMethodProcessor());

		// Custom return value types
		if (getCustomReturnValueHandlers() != null) {
			handlers.addAll(getCustomReturnValueHandlers());
		}

		// Catch-all
       // 兜底的返回值解析器 
		handlers.add(new ModelAttributeMethodProcessor(true));

		return handlers;
	}


	/**
	 * Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception.
	 */
	@Override
	@Nullable
	protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
			HttpServletResponse response, @Nullable HandlerMethod handlerMethod, 
			Exception exception) {

       // 考虑发生异常 exception 的控制器方法的所在类,以及所有的 @ControllerAdvice 类中的
       // 所有 @ExceptionHandler 异常处理控制器方法,看看有没有能处理该异常的异常处理控制器方法,
       // 如果有则继续,如果没有,则返回 null,调用者会考虑其他方案
		ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(
				handlerMethod, exception);
		if (exceptionHandlerMethod == null) {
			return null;
		}

       // 现在已经找到能处理异常 exception 的异常处理控制器方法了,包装保存在 
       // exceptionHandlerMethod 中, 现在准备调用它 :
       // 1. 设置调用所需要的工具 : 参数解析器,返回值处理器
       // 2. 准备目标异常处理控制器方法执行过程中需要保存指定决定的容器对象 mavContainer
       // 3. 调用目标异常处理控制器方法
		if (this.argumentResolvers != null) {
			exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
			exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}

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

       // 调用目标异常处理控制器方法并做异常处理 
		try {
			if (logger.isDebugEnabled()) {
				logger.debug("Using @ExceptionHandler " + 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("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
			}
			// Continue with default processing of the original exception...
          // 异常处理控制器方法执行遇到异常,这里返回null,让调用者继续使用缺省方式处理
          // 原来需要处理的异常,注意,指的是参数 exception,而不是这里抓到的异常
          // invocationEx
			return null;
		}

        
		if (mavContainer.isRequestHandled()) {
           // 异常处理控制器方法将请求处理完全了,这里返回空  ModelAndView() 对象,
           // 告诉调用者不需要继续解析和渲染视图了
			return new ModelAndView();
		}
		else {
          //  异常处理控制器方法正常执行结果,但没有将请求处理完全,现在构造 ModelAndView,
          // 调用者需要继续解析和渲染视图
			ModelMap model = mavContainer.getModel();
			HttpStatus status = mavContainer.getStatus();
			ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
			mav.setViewName(mavContainer.getViewName());
			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;
		}
	}

	/**
	 * Find an {@code @ExceptionHandler} method for the given exception. The default
	 * implementation searches methods in the class hierarchy of the controller first
	 * and if not found, it continues searching for additional {@code @ExceptionHandler}
	 * methods assuming some {@linkplain ControllerAdvice @ControllerAdvice}
	 * Spring-managed beans were detected.
	 * @param handlerMethod the method where the exception was raised (may be {@code null})
	 * @param exception the raised exception
	 * @return a method to handle the exception, or {@code null} if none
	 */
	@Nullable
	protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
			@Nullable HandlerMethod handlerMethod, Exception exception) {

		Class<?> handlerType = null;

		if (handlerMethod != null) {
			// Local exception handler methods on the controller class itself.
			// To be invoked through the proxy, even in case of an interface-based proxy.
          // 首先尝试分析控制器方法所在类类自身是否有使用注解 @ExceptionHander,
          // 这一分析动作跟分析一个@ControllerAdvice组件类是一样的,也是使用
          // ExceptionHandlerMethodResolver
			handlerType = handlerMethod.getBeanType();
			ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
			if (resolver == null) {
				resolver = new ExceptionHandlerMethodResolver(handlerType);
				this.exceptionHandlerCache.put(handlerType, resolver);
			}
            
           // 现在看控制方法所在类是否有能处理指定异常 exception 的异常处理控制器方法,
           // 有的话使用该方法构建 ServletInvocableHandlerMethod, 用于处理该异常
			Method method = resolver.resolveMethod(exception);
			if (method != null) {
				return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
			}
			// For advice applicability check below (involving base packages, assignable types
			// and annotation presence), use target class instead of interface-based proxy.
			if (Proxy.isProxyClass(handlerType)) {
				handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
			}
		}

       // 如果控制器方法所在类不存在能处理指定异常 exception 的异常处理控制器方法,
       // 现在查看所有的 @ControllerAdvice 类中是否有能处理指定异常 exception
       // 的异常处理控制器方法,找到的话相应地构造 ServletInvocableHandlerMethod 
       // 并返回,用于处理该异常
		for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry 
				: this.exceptionHandlerAdviceCache.entrySet()) {
			ControllerAdviceBean advice = entry.getKey();
			if (advice.isApplicableToBeanType(handlerType)) {
				ExceptionHandlerMethodResolver resolver = entry.getValue();
				Method method = resolver.resolveMethod(exception);
				if (method != null) {
					return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
				}
			}
		}

       // 从发生异常 exception 的控制器方法所在类和所有的  @ControllerAdvice 类中
       // 都找不到合适的异常处理控制器方法,现在返回 null,意思是告知调用者:我处理
       // 不了这一个异常,你再看看其他 HandlerExceptionResolver ?
		return null;
	}

}

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值