精盡Spring MVC原始碼分析 - HandlerExceptionResolver 元件

月圓吖發表於2020-12-22

該系列文件是本人在學習 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 介面體系的結構如下:

精盡Spring MVC原始碼分析 - 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 實現類
精盡Spring MVC原始碼分析 - 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 原始碼分析》

相關文章