精盡Spring MVC源碼分析 – HandlerExceptionResolver 組件

該系列文檔是本人在學習 Spring MVC 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀

Spring 版本:5.2.4.RELEASE

該系列其他文檔請查看:《精盡 Spring MVC 源碼分析 – 文章導讀》

HandlerExceptionResolver 組件

HandlerExceptionResolver 組件,處理器異常解析器,將處理器( handler )執行時發生的異常(也就是處理請求,執行方法的過程中)解析(轉換)成對應的 ModelAndView 結果

回顧

先來回顧一下在 DispatcherServlet 中處理請求的過程中哪裡使用到 HandlerExceptionResolver 組件,可以回到《一個請求的旅行過程》中的 DispatcherServletprocessHandlerException 方法中看看,如下:

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
        @Nullable Object handler, Exception ex) throws Exception {

    // Success and error responses may use different content types
    // 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 屬性
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

    // Check registered HandlerExceptionResolvers...
    // <a> 遍歷 HandlerExceptionResolver 數組,解析異常,生成 ModelAndView 對象
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        // 遍歷 HandlerExceptionResolver 數組
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            // 解析異常,生成 ModelAndView 對象
            exMv = resolver.resolveException(request, response, handler, ex);
            // 生成成功,結束循環
            if (exMv != null) {
                break;
            }
        }
    }
    // <b> 情況一,生成了 ModelAndView 對象,進行返回
    if (exMv != null) {
        // ModelAndView 對象為空,則返回 null
        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;
    }
    // <c> 情況二,未生成 ModelAndView 對象,則拋出異常
    throw ex;
}

在 Spring MVC 的 DispatcherServlet 處理請求執行方法過程中,不管是否拋出異常都會進行結果處理,如果拋出了異常也需要調用該方法處理異常

可以看到,在 <a> 處會遍歷所有的 HandlerExceptionResolver 異常處理器來處理,如果某一個處理器處理成功並返回 ModelAndView 對象,則直接返回

HandlerExceptionResolver 介面

org.springframework.web.servlet.HandlerExceptionResolver,異常處理器介面,程式碼如下:

public interface HandlerExceptionResolver {
	/**
	 * 解析異常,轉換成對應的 ModelAndView 結果
	 */
	@Nullable
	ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

HandlerExceptionResolver 介面體系的結構如下:

初始化過程

DispatcherServletinitHandlerExceptionResolvers(ApplicationContext context) 方法,初始化 HandlerExceptionResolver 組件,方法如下:

private void initHandlerExceptionResolvers(ApplicationContext context) {
    // 置空 handlerExceptionResolvers 處理
    this.handlerExceptionResolvers = null;

    // 情況一,自動掃描 HandlerExceptionResolver 類型的 Bean 們
    if (this.detectAllHandlerExceptionResolvers) {
        // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
            // We keep HandlerExceptionResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    // 情況二,獲得名字為 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME 的 Bean
    else {
        try {
            HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

    // Ensure we have at least some HandlerExceptionResolvers, by registering
    // default HandlerExceptionResolvers if no other resolvers are found.
    /**
     * 情況三,如果未獲得到,則獲得默認配置的 HandlerExceptionResolver 類
     * {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver}
     * {@link	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}
     * {@link	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}
     */
    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}
  1. 如果「開啟」探測功能,則掃描已註冊的 HandlerExceptionResolver 的 Bean 們,添加到 handlerExceptionResolvers 中,默認開啟

  2. 如果「關閉」探測功能,則獲得 Bean 名稱為 “handlerExceptionResolver” 對應的 Bean ,將其添加至 handlerExceptionResolvers

  3. 如果未獲得到,則獲得默認配置的 HandlerExceptionResolver 類,調用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是從 DispatcherServlet.properties 文件中讀取 HandlerExceptionResolver 的默認實現類,如下:

    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    

在 Spring Boot 中,默認配置下會走上述 1 的邏輯,handlerExceptionResolvers 有兩個元素:

  • org.springframework.boot.autoconfigure.web.DefaultErrorAttributes:在 Spring Boot 中,邏輯比較簡單,暫時忽略
  • org.springframework.web.servlet.handler.HandlerExceptionResolverComposite:複合的 HandlerExceptionResolver 實現類

接下來會對 HandlerExceptionResolverComposite 中的這三種異常處理器進行分析

HandlerExceptionResolverComposite

org.springframework.web.servlet.handler.HandlerExceptionResolverComposite,實現 HandlerExceptionResolver、Ordered 介面,複合的 HandlerExceptionResolver 實現類

構造方法

public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
	/**
	 * 異常解析器數組
	 */
	@Nullable
	private List<HandlerExceptionResolver> resolvers;
	/**
	 * 優先順序,默認最低
	 */
	private int order = Ordered.LOWEST_PRECEDENCE;
}
  • resolvers:HandlerExceptionResolver 實現類列表
  • order:優先順序,默認最低

從上面的初始化過程中可以看到,Spring Boot 默認配置下 HandlerExceptionResolverComposite 包含三個實現類:

  1. org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
  2. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
  3. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

resolveException

實現 resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 方法,遍歷 HandlerExceptionResolver 數組,逐個處理異常 ex,如果成功,則返回 ModelAndView 對象,方法如下:

@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
                                     @Nullable Object handler, Exception ex) {
    if (this.resolvers != null) {
        for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
            ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (mav != null) {
                return mav;
            }
        }
    }
    return null;
}

AbstractHandlerExceptionResolver

org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver,實現 HandlerExceptionResolver、Ordered 介面,HandlerExceptionResolver 抽象類,作為所有 HandlerExceptionResolver 實現類的基類

構造方法

public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {

	private static final String HEADER_CACHE_CONTROL = "Cache-Control";
	/**
	 * 優先順序,默認最低
	 */
	private int order = Ordered.LOWEST_PRECEDENCE;
	/**
	 * 匹配的處理器對象的集合
	 */
	@Nullable
	private Set<?> mappedHandlers;
	/**
	 * 匹配的處理器類型的數組
	 */
	@Nullable
	private Class<?>[] mappedHandlerClasses;
	/**
	 * 防止響應快取
	 */
	private boolean preventResponseCaching = false;
}

上面的這些屬性在後續方法中會講到

shouldApplyTo

shouldApplyTo(HttpServletRequest request, Object handler) 方法,判斷當前 HandlerExceptionResolver 是否能應用到傳入的 handler 處理器,方法如下:

protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    if (handler != null) {
        // <1> 如果 mappedHandlers 包含 handler 對象,則返回 true
        if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
            return true;
        }
        // <2> 如果 mappedHandlerClasses 包含 handler 的類型,則返回 true
        if (this.mappedHandlerClasses != null) {
            for (Class<?> handlerClass : this.mappedHandlerClasses) {
                if (handlerClass.isInstance(handler)) {
                    return true;
                }
            }
        }
    }
    // Else only apply if there are no explicit handler mappings.
    // <3> 如果 mappedHandlers 和 mappedHandlerClasses 都為空,說明直接匹配
    return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
  1. 如果 mappedHandlers 包含該 handler 處理器對象,則返回 true
  2. 如果 mappedHandlerClasses 包含該 handler 處理器所在類,則返回 true
  3. 如果 mappedHandlersmappedHandlerClasses 都為空,說明直接匹配

prepareResponse

prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止響應快取,方法如下:

protected void prepareResponse(Exception ex, HttpServletResponse response) {
    if (this.preventResponseCaching) {
        preventCaching(response);
    }
}

/**
 * Prevents the response from being cached, through setting corresponding
 * HTTP {@code Cache-Control: no-store} header.
 * @param response current HTTP response
 */
protected void preventCaching(HttpServletResponse response) {
    response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}

如果想要阻止響應快取,需要設置 preventResponseCachingtrue

resolveException

實現 resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,程式碼如下:

@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, 
                                     @Nullable Object handler, Exception ex) {
    // <1> 判斷是否可以應用
    if (shouldApplyTo(request, handler)) {
        // <1.1> 阻止快取
        prepareResponse(ex, response);
        // <1.2> 執行解析異常,返回 ModelAndView 對象
        ModelAndView result = doResolveException(request, response, handler, ex);
        // <1.3> 如果 ModelAndView 對象非空,則列印日誌
        if (result != null) {
            // Print debug message when warn logger is not enabled.
            if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
                logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
            }
            // Explicitly configured warn logger in logException method.
            logException(ex, request);
        }
        // <1.4> 返回執行結果
        return result;
    }
    // <2> 不可應用,直接返回 null
    else {
        return null;
    }
}

@Nullable
protected abstract ModelAndView doResolveException(
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
  1. 調用 shouldApplyTo(HttpServletRequest request, Object handler) 方法,判斷是否可以應用,如果可以應用

    1. 調用 prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止快取
    2. 調用 doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 抽象方法,執行解析異常,返回 ModelAndView 對象
    3. 如果 ModelAndView 對象非空,則列印日誌
    4. 返回執行結果
  2. 不可應用,直接返回 null

AbstractHandlerMethodExceptionResolver

org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver,繼承 AbstractHandlerExceptionResolver 抽象類,基於 handler 類型為 HandlerMethod 的 HandlerExceptionResolver 抽象類。

可能你會有疑惑,為什麼 AbstractHandlerMethodExceptionResolver 只有一個 ExceptionHandlerExceptionResolver 子類,為什麼還要做抽象呢?因為 ExceptionHandlerExceptionResolver 是基於 @ExceptionHandler 註解來配置對應的異常處理器,而如果未來我們想自定義其它的方式來配置對應的異常處理器,就可以來繼承 AbstractHandlerMethodExceptionResolver 這個抽象類。😈

有沒發現 Spring MVC 中,存在大量的邏輯與配置分離的分層實現,嘻嘻~:happy:

shouldApplyTo

重寫 shouldApplyTo(HttpServletRequest request, Object handler) 方法,程式碼如下:

@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    // 情況一,如果 handler 為空,則直接調用父方法
    if (handler == null) {
        return super.shouldApplyTo(request, null);
    }
    // 情況二,處理 handler 為 HandlerMethod 類型的情況
    else if (handler instanceof HandlerMethod) {
        // <x> 獲得真正的 handler
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        handler = handlerMethod.getBean();
        // 調用父方法
        return super.shouldApplyTo(request, handler);
    }
    // 情況三,直接返回 false
    else {
        return false;
    }
}

重點在於情況二,需要在 <x> 處,調用 HandlerMethod#getBean() 方法,獲得真正的 handler 處理器。

doResolveException

重寫 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,程式碼如下:

@Override
@Nullable
protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, 
                                                @Nullable Object handler, Exception ex) {
    return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}

@Nullable
protected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, 
                                                                @Nullable HandlerMethod handlerMethod, Exception ex);

handler 轉換成 HandlerMethod 類型,並提供新的抽象方法

【重點】ExceptionHandlerExceptionResolver

org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,實現 ApplicationContextAware、InitializingBean 介面,繼承 AbstractHandlerMethodExceptionResolver 抽象類,基於 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 實現類。

示例

可能你沒有使用 @ExceptionHandler 註解來實現過異常的處理,例如:

@Log4j2
@RestControllerAdvice
public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({EmptyArgumentException.class, IllegalArgumentException.class})
    public Result<?> customizeHandleArgumentException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
        response.setStatus(HttpStatus.OK.value());
        return Result.fail(ResultCode.PARAM_ERROR.getCode(), e.getMessage());
    }

    @ExceptionHandler({Exception.class})
    public Result<?> customizeHandleException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
        log.error("異常攔截[{}]:", e.getMessage(), e);
        response.setStatus(HttpStatus.OK.value());
        return Result.fail(ResultCode.UNKNOWN.getCode(), e.getMessage());
    }
}

該自定義異常處理類會處理 Controller 類拋出的指定類型的異常

構造方法

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;
	/*
	 * HTTP 消息轉換器
	 */
	private List<HttpMessageConverter<?>> messageConverters;

	private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
	/**
	 * 響應體的後置增強器
	 */
	private final List<Object> responseBodyAdvice = new ArrayList<>();

	@Nullable
	private ApplicationContext applicationContext;

	private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
	private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();

	public ExceptionHandlerExceptionResolver() {
		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
		stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316
		// 初始化 messageConverters
		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());
	}
}

有沒有一種熟悉的感覺,和 《HandlerAdapter 組件(一)之 HandlerAdapter》RequestMappingHandlerAdapter 類似,有大量的相同變數,例如參數解析器和返回結果處理器,最終也是調用 ServletInvocableHandlerMethod 的方法。因為你定義也是定義的方法去處理相關的異常😈 往下看

afterPropertiesSet

因為 ExceptionHandlerExceptionResolver 實現了 InitializingBean 介面,在 Sping 初始化該 Bean 的時候,會調用該方法,完成一些初始化工作,方法如下:

@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    // 初始化 exceptionHandlerAdviceCache、responseBodyAdvice
    initExceptionHandlerAdviceCache();

    // 初始化 argumentResolvers 參數
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 初始化 returnValueHandlers 參數
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}
  1. 調用 initExceptionHandlerAdviceCache() 方法,初始化 exceptionHandlerAdviceCacheresponseBodyAdvice,詳情見下文
  2. 初始化 argumentResolvers 屬性。其中,#getDefaultArgumentResolvers() 方法,獲得默認的 HandlerMethodArgumentResolver 數組,詳情見下文
  3. 初始化 returnValueHandlers 屬性。其中,#getDefaultReturnValueHandlers() 方法,獲得默認的 HandlerMethodReturnValueHandler 數組,詳情見下文

initExceptionHandlerAdviceCache

initExceptionHandlerAdviceCache() 方法,初始化 exceptionHandlerAdviceCacheresponseBodyAdvice,方法如下:

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

    // <1> 掃描 @ControllerAdvice 註解的 Bean 們,並將進行排序
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);

    // <2> 遍歷 ControllerAdviceBean 數組
    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        // <2.1> 掃描該 ControllerAdviceBean 對應的類型
        ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
        // <2.2> 有 @ExceptionHandler 註解,則添加到 exceptionHandlerAdviceCache 中
        if (resolver.hasExceptionMappings()) {
            this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
        }
        // <2.3> 如果該 beanType 類型是 ResponseBodyAdvice 子類,則添加到 responseBodyAdvice 中
        if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            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");
        }
    }
}
  1. 調用 ControllerAdviceBeanfindAnnotatedBeans(ApplicationContext context) 方法,掃描 @ControllerAdvice 註解的 Bean 們,並將進行排序,這裡就會掃描到上面示例中 CustomizeExceptionHandler 自定義異常處理類

  2. 遍歷 ControllerAdviceBean 數組

    1. 創建掃描該 ControllerAdviceBean 對應的類型 ExceptionHandlerMethodResolver 對象 resolver,該對象在下面會分析
    2. @ExceptionHandler 註解,則將resolver添加到 exceptionHandlerAdviceCache
    3. 如果該 beanType 類型是 ResponseBodyAdvice 子類,則添加到 responseBodyAdvice

getDefaultArgumentResolvers

getDefaultArgumentResolvers() 方法,獲得默認的 HandlerMethodArgumentResolver 數組,方法如下:

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

getDefaultReturnValueHandlers

getDefaultReturnValueHandlers() 方法,獲得默認的 HandlerMethodReturnValueHandler 數組,方法如下:

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

ExceptionHandlerMethodResolver 類

在 ExceptionHandlerExceptionResolver 的 initExceptionHandlerAdviceCache 方法中會用到,兩者的名字太容易混淆了

org.springframework.web.method.annotation.ExceptionHandlerMethodResolver,添加 @ControllerAdvice 註解的 Bean,用於解析添加了 @ExceptionHandler 註解的方法

構造方法
public class ExceptionHandlerMethodResolver {
	/**
	 * A filter for selecting {@code @ExceptionHandler} methods.
	 * 
	 * MethodFilter 對象,用於過濾帶有 @ExceptionHandler 註解的方法
	 */
	public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
			AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
	/**
	 * 已經映射的方法
	 *
	 * 在 {@link #ExceptionHandlerMethodResolver(Class)} 構造方法中初始化
	 */
	pivate final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
	/**
	 * 已經匹配的方法
	 *
	 * 在 {@link #resolveMethod(Exception)} 方法中初始化
	 */
	private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);

	public ExceptionHandlerMethodResolver(Class<?> handlerType) {
		// <1> 遍歷 @ExceptionHandler 註解的方法,這些方法用於處理對應的異常
		for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
			// <2> 遍歷處理的異常集合,獲取到該方法能處理哪些異常
			for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
				// <3> 添加到 mappedMethods 中
				addExceptionMapping(exceptionType, method);
			}
		}
	}
}

mappedMethodsexceptionLookupCache 差別在於,後者是經過查找,比較優先順序之後所產生的

  1. 遍歷 @ExceptionHandler 註解的方法

  2. 調用 detectExceptionMappings(Method method) 方法,獲得方法的異常數組,如下:

    private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
        List<Class<? extends Throwable>> result = new ArrayList<>();
        // 首先,從方法上的 @ExceptionHandler 註解中,獲得要處理的異常類型,添加到 result 中
        detectAnnotationExceptionMappings(method, result);
        // 其次,如果獲取不到,從方法參數中,獲得所處理的異常,添加到 result 中
        if (result.isEmpty()) {
            for (Class<?> paramType : method.getParameterTypes()) {
                if (Throwable.class.isAssignableFrom(paramType)) {
                    result.add((Class<? extends Throwable>) paramType);
                }
            }
        }
        // 如果獲取不到,則拋出 IllegalStateException 異常
        if (result.isEmpty()) {
            throw new IllegalStateException("No exception types mapped to " + method);
        }
        return result;
    }
    
    private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
        ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
        Assert.state(ann != null, "No ExceptionHandler annotation");
        result.addAll(Arrays.asList(ann.value()));
    }
    
  3. 調用 addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) 方法,添加到 mappedMethods 中,如下:

    private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
        // 添加到 mappedMethods 中
        Method oldMethod = this.mappedMethods.put(exceptionType, method);
        // 如果已存在,說明衝突,所以拋出 IllegalStateException 異常
        if (oldMethod != null && !oldMethod.equals(method)) {
            throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
                    exceptionType + "]: {" + oldMethod + ", " + method + "}");
        }
    }
    
hasExceptionMappings

hasExceptionMappings() 方法,判斷 mappedMethods 非空,方法如下:

public boolean hasExceptionMappings() {
    return !this.mappedMethods.isEmpty();
}
resolveMethod

resolveMethod(Exception exception) 方法,獲取解析異常對應的方法,方法如下:

@Nullable
public Method resolveMethod(Exception exception) {
    return resolveMethodByThrowable(exception);
}
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
    // 首先,獲得異常對應的方法
    Method method = resolveMethodByExceptionType(exception.getClass());
    // 其次,獲取不到,則使用異常 cause 對應的方法
    if (method == null) {
        Throwable cause = exception.getCause();
        if (cause != null) {
            method = resolveMethodByExceptionType(cause.getClass());
        }
    }
    return method;
}

按照 exceptionexception.cause 的先後,調用 resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) 方法,獲得異常對應的方法,如下:

@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
    // 首先,先從 exceptionLookupCache 快取中獲得異常對應的處理方法
    Method method = this.exceptionLookupCache.get(exceptionType);
    // 其次,獲取不到,則從 mappedMethods 中獲得,並添加到 exceptionLookupCache 中
    if (method == null) {
        method = getMappedMethod(exceptionType);
        this.exceptionLookupCache.put(exceptionType, method);
    }
    return method;
}

邏輯比較簡單,調用 getMappedMethod(Class<? extends Throwable> exceptionType) 方法,獲得異常對應的方法,如下:

@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
    List<Class<? extends Throwable>> matches = new ArrayList<>();
    // 遍歷 mappedMethods 數組,匹配異常,添加到 matches 中
    for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
        if (mappedException.isAssignableFrom(exceptionType)) {
            matches.add(mappedException);
        }
    }
    // 將匹配的結果,排序,選擇第一個
    if (!matches.isEmpty()) {
        matches.sort(new ExceptionDepthComparator(exceptionType));
        return this.mappedMethods.get(matches.get(0));
    }
    else {
        return null;
    }
}

邏輯比較簡單,關於 org.springframework.core.ExceptionDepthComparator 比較器,胖友自己點擊 傳送門 查看。大體的邏輯是,比較它們和目標類的繼承層級,越小越匹配。

getExceptionHandlerMethod

getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) 方法,獲得異常對應的 ServletInvocableHandlerMethod 對象,程式碼如下:

@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
        @Nullable HandlerMethod handlerMethod, Exception exception) {

    // 處理器的類型
    Class<?> handlerType = null;

    // <1> 首先,如果 handlerMethod 非空,則先獲得 Controller 對應的 @ExceptionHandler 處理器對應的方法
    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.
        // 獲得 handlerType
        handlerType = handlerMethod.getBeanType();
        // 獲得 handlerType 對應的 ExceptionHandlerMethodResolver 對象
        ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
        if (resolver == null) {
            resolver = new ExceptionHandlerMethodResolver(handlerType);
            this.exceptionHandlerCache.put(handlerType, resolver);
        }
        // 獲得異常對應的 Method 處理方法
        Method method = resolver.resolveMethod(exception);
        // 如果獲得該異常對應的 Method 處理方法,則創建 ServletInvocableHandlerMethod 對象,並返回
        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.
        // 獲得 handlerType 的原始類。因為,此處有可能是代理對象
        if (Proxy.isProxyClass(handlerType)) {
            handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
        }
    }

    // <2> 其次,使用 ControllerAdvice 對應的 @ExceptionHandler 處理器對應的方法
    for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
        ControllerAdviceBean advice = entry.getKey();
        // 如果 ControllerAdvice 支援當前的 handlerType
        if (advice.isApplicableToBeanType(handlerType)) {
            // 獲得 handlerType 對應的 ExceptionHandlerMethodResolver 對象
            ExceptionHandlerMethodResolver resolver = entry.getValue();
            // 獲得異常對應的 Method 處理方法
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
            }
        }
    }

    // 最差,獲取不到
    return null;
}
  1. 首先,如果 handlerMethod 非空,則先獲得 Controller 對應的 @ExceptionHandler 處理器對應的方法,如果獲取到了,則將該 Method 封裝成 ServletInvocableHandlerMethod 對象並返回

  2. 其次,使用 ControllerAdvice 對應的 @ExceptionHandler 處理器對應的方法,如果獲取到了,則將該 Method 封裝成 ServletInvocableHandlerMethod 對象並返回

  3. 最差,獲取不到,返回 null

上面第 2 種情況也就是示例中定義的方法哦~

doResolveHandlerMethodException

實現 doResolveHandlerMethodException(ttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) 方法,處理異常,程式碼如下:

@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
        HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
    // <1> 獲得異常對應的 ServletInvocableHandlerMethod 對象
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
    if (exceptionHandlerMethod == null) {
        return null;
    }

    // <1.1> 設置 ServletInvocableHandlerMethod 對象的相關屬性
    if (this.argumentResolvers != null) {
        exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
        exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }

    // <1.2> 創建 ServletWebRequest 對象
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    // <1.3> 創建 ModelAndViewContainer 對象
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();

    try {
        if (logger.isDebugEnabled()) {
            logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
        }
        // <2> // 執行處理該異常的方法 ServletInvocableHandlerMethod 的調用
        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).
        // <2.1> 發生異常,則直接返回
        if (invocationEx != exception && logger.isWarnEnabled()) {
            logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
        }
        // Continue with default processing of the original exception...
        return null;
    }

    // <3> 如果 mavContainer 已處理,則返回 '空的' ModelAndView 對象。
    if (mavContainer.isRequestHandled()) {
        return new ModelAndView();
    }
    // <4> 如果 mavContainer 未處,則基於 `mavContainer` 生成 ModelAndView 對象
    else {
        ModelMap model = mavContainer.getModel();
        HttpStatus status = mavContainer.getStatus();
        // <4.1> 創建 ModelAndView 對象,並設置相關屬性
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
        mav.setViewName(mavContainer.getViewName());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        // <4.2>
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
        return mav;
    }
}
  1. 調用 getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) 方法,獲得異常對應的 ServletInvocableHandlerMethod 對象

    1. 設置 ServletInvocableHandlerMethod 對象的相關屬性,參數解析器,返回結果處理器
    2. 創建 ServletWebRequest 對象 webRequest,封裝了請求和響應
    3. 創建 ModelAndViewContainer 對象 mavContainer,用於獲取 ModelAndView 對象
  2. 執行處理該異常的方法,ServletInvocableHandlerMethod 對象的調用

    1. 發生異常,則直接返回
  3. 如果 mavContainer 已處理,則返回 「空的」 ModelAndView 對象。😈 這樣,就不會被後續的 ViewResolver 所處理。為什麼呢?可以自己回看下 DispatcherServlet 的 processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,很容易明白

  4. 如果 mavContainer 未處理,則基於 mavContainer 生成 ModelAndView 對象

    1. 創建 ModelAndView 對象,並設置相關屬性,視圖名稱
    2. FlashMapManager 相關,暫時忽略

ResponseStatusExceptionResolver

org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,實現 MessageSourceAware 介面,繼承 AbstractHandlerExceptionResolver 抽象類,基於 @ResponseStatus 提供錯誤響應的 HandlerExceptionResolver 實現類

構造方法

public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
	@Nullable
	private MessageSource messageSource;
}

applyStatusAndReason

applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) 方法,設置錯誤響應,方法如下:

protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
        throws IOException {

    // 情況一,如果無錯誤提示,則響應只設置狀態碼
    if (!StringUtils.hasLength(reason)) {
        response.sendError(statusCode);
    }
    // 情況二,如果有錯誤資訊,則響應設置狀態碼 + 錯誤提示
    else {
        // 進一步解析錯誤提示,如果有 messageSource 的情況下
        String resolvedReason = (this.messageSource != null ?
                this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
                reason);
        // 設置
        response.sendError(statusCode, resolvedReason);
    }
    // 創建「空」 ModelAndView 對象,並返回
    return new ModelAndView();
}

注意,此處返回的也是「空」的 ModelAndView 對象。這樣,就不會被後續的 ViewResolver 所處理

doResolveException

實現 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,程式碼如下:

@Override
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, 
                                          @Nullable Object handler, Exception ex) {
    try {
        // <1> 情況一,如果異常是 ResponseStatusException 類型,進行解析並設置到響應
        if (ex instanceof ResponseStatusException) {
            return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
        }

        // <2> 情況二,如果有 @ResponseStatus 註解,進行解析並設置到響應
        ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
        if (status != null) {
            return resolveResponseStatus(status, request, response, handler, ex);
        }

        // <3> 情況三,使用異常的 cause 在走一次情況一、情況二的邏輯。
        if (ex.getCause() instanceof Exception) {
            return doResolveException(request, response, handler, (Exception) ex.getCause());
        }
    }
    catch (Exception resolveEx) {
        if (logger.isWarnEnabled()) {
            logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
        }
    }
    return null;
}
  1. 情況一,如果異常是 ResponseStatusException 類型,進行解析並設置到響應,調用 resolveResponseStatus(ResponseStatusException ex, HttpServletRequest request, HttpServletResponse response, Object handler) 方法,如下:

    protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws Exception {
        int statusCode = ex.getStatus().value();
        String reason = ex.getReason();
        return applyStatusAndReason(statusCode, reason, response);
    }
    
  2. 情況二,如果有 @ResponseStatus 註解,進行解析並設置到響應,調用 resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,如下:

    protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
            HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
        int statusCode = responseStatus.code().value();
        String reason = responseStatus.reason();
        return applyStatusAndReason(statusCode, reason, response);
    }
    
  3. 情況三,使用異常的 cause 再走一次情況一情況二的邏輯

DefaultHandlerExceptionResolver

org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver,繼承 AbstractHandlerExceptionResolver 抽象類,默認 HandlerExceptionResolver 實現類,針對各種異常,設置錯誤響應碼

其中,實現 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,程式碼如下:

@Override
@Nullable
protected ModelAndView doResolveException(
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    try {
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return handleHttpRequestMethodNotSupported(
                    (HttpRequestMethodNotSupportedException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMediaTypeNotSupportedException) {
            return handleHttpMediaTypeNotSupported(
                    (HttpMediaTypeNotSupportedException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMediaTypeNotAcceptableException) {
            return handleHttpMediaTypeNotAcceptable(
                    (HttpMediaTypeNotAcceptableException) ex, request, response, handler);
        }
        else if (ex instanceof MissingPathVariableException) {
            return handleMissingPathVariable(
                    (MissingPathVariableException) ex, request, response, handler);
        }
        else if (ex instanceof MissingServletRequestParameterException) {
            return handleMissingServletRequestParameter(
                    (MissingServletRequestParameterException) ex, request, response, handler);
        }
        else if (ex instanceof ServletRequestBindingException) {
            return handleServletRequestBindingException(
                    (ServletRequestBindingException) ex, request, response, handler);
        }
        else if (ex instanceof ConversionNotSupportedException) {
            return handleConversionNotSupported(
                    (ConversionNotSupportedException) ex, request, response, handler);
        }
        else if (ex instanceof TypeMismatchException) {
            return handleTypeMismatch(
                    (TypeMismatchException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMessageNotReadableException) {
            return handleHttpMessageNotReadable(
                    (HttpMessageNotReadableException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMessageNotWritableException) {
            return handleHttpMessageNotWritable(
                    (HttpMessageNotWritableException) ex, request, response, handler);
        }
        else if (ex instanceof MethodArgumentNotValidException) {
            return handleMethodArgumentNotValidException(
                    (MethodArgumentNotValidException) ex, request, response, handler);
        }
        else if (ex instanceof MissingServletRequestPartException) {
            return handleMissingServletRequestPartException(
                    (MissingServletRequestPartException) ex, request, response, handler);
        }
        else if (ex instanceof BindException) {
            return handleBindException((BindException) ex, request, response, handler);
        }
        else if (ex instanceof NoHandlerFoundException) {
            return handleNoHandlerFoundException(
                    (NoHandlerFoundException) ex, request, response, handler);
        }
        else if (ex instanceof AsyncRequestTimeoutException) {
            return handleAsyncRequestTimeoutException(
                    (AsyncRequestTimeoutException) ex, request, response, handler);
        }
    }
    catch (Exception handlerEx) {
        if (logger.isWarnEnabled()) {
            logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
        }
    }
    return null;
}

邏輯不複雜,根據不同的異常,設置響應碼和錯誤資訊,例如 HTTP 方法類型不支援,如下:

protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {

    String[] supportedMethods = ex.getSupportedMethods();
    if (supportedMethods != null) {
        response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
    }
    // 405 狀態碼,HTTP Method 不支援
    response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
    return new ModelAndView();
}

注意,返回的都是「空」的 ModelAndView 對象。這樣,就不會被後續的 ViewResolver 所處理

總結

本文對 Spring MVC 中的 HandlerExceptionResolver 組件進行分析,處理器異常解析器,將處理器( handler )執行時發生的異常(也就是處理請求,執行方法的過程中發生的異常)解析(轉換)成對應的 ModelAndView 結果

HandlerExceptionResolver 的實現類沒有特別多,不過也採用了組合模式,如果某個異常處理器進行處理了,也就是返回的 ModeAndView 不為 null(一般都是「空」對象),則直接返回該 ModeAndView 對象

在 Spring MVC 和 Spring Boot 中,默認情況下都有三種 HandlerExceptionResolver 實現類,他們的順序如下:

  1. ExceptionHandlerExceptionResolver:基於 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 實現類。例如通過 @ControllerAdvice 註解自定義異常處理器,加上@ExceptionHandler註解指定方法所需要處理的異常類型,這種方式就在這個實現類中實現的。沒有使用過這兩個註解可以參考上面的示例
  2. ResponseStatusExceptionResolver:基於 @ResponseStatus 提供錯誤響應的 HandlerExceptionResolver 實現類。例如在方法上面添加 @ResponseStatus 註解,指定該方法發生異常時,需要設置的 code 響應碼和 reason 錯誤資訊
  3. DefaultHandlerExceptionResolver:默認 HandlerExceptionResolver 實現類,針對各種異常,設置錯誤響應碼。例如 HTTP Method 不支援,則在這個實現類中往響應中設置錯誤碼錯誤資訊

到這裡,已經分析了 Spring MVC 的 DispatcherServlet,以及 MultipartResolver、HandlerMapping、HandlerAdapter 和 HandlerExceptionResolver 四個組件,只想說:Spring MVC 的設計者太膩害了~😛

參考文章:芋道源碼《精盡 Spring MVC 源碼分析》