主頁 > 後端開發 > 精盡Spring MVC原始碼分析 - HandlerExceptionResolver 組件

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

2020-12-22 14:01:31 後端開發

該系列檔案是本人在學習 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 物件的呼叫

    • 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》中已經詳細的分析過了
    • 此處傳入了 Object... providedArgs 引數為 exceptionhandlerMethod 變數,這也是為什么 @ExceptionHanlder 注解的方法,可以設定為這兩個引數??
    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 原始碼分析》

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/238670.html

標籤:其他

上一篇:這個 bug 讓我更加理解 Spring 單例了

下一篇:java OA辦公管理系統 Springboot vue.js 前后分離 跨域 作業流 集成代碼生成器

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more