新聞中心
SpringMVC 中針對(duì)異常問(wèn)題有一套完整的處理體系,這套體系非常好用,今天松哥就花點(diǎn)時(shí)間來(lái)和大家聊一聊 SpringMVC 中的異常處理體系,我們把 SpringMVC 中的異常體系從頭到尾梳理一下。

創(chuàng)新互聯(lián)公司專注于南召企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站,商城網(wǎng)站制作。南召網(wǎng)站建設(shè)公司,為南召等地區(qū)提供建站服務(wù)。全流程按需策劃設(shè)計(jì),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)
1.異常解析器概覽
在 SpringMVC 的異常體系中,處于最頂層的大 Boss 是 HandlerExceptionResolver,這是一個(gè)接口,里邊只有一個(gè)方法:
- public interface HandlerExceptionResolver {
- @Nullable
- ModelAndView resolveException(
- HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
- }
resolveException 方法就用來(lái)解析請(qǐng)求處理過(guò)程中所產(chǎn)生的異常,并最終返回一個(gè) ModelAndView。
我們來(lái)看下 HandlerExceptionResolver 的實(shí)現(xiàn)類:
直接實(shí)現(xiàn) HandlerExceptionResolver 接口的類有三個(gè):
- HandlerExceptionResolverComposite:這個(gè)一看又是一個(gè)組合,在最近的源碼分析中我們已經(jīng)多次見(jiàn)到 xxxComposite 了,這里就不再贅述。
- DefaultErrorAttributes:這個(gè)用來(lái)保存異常屬性。
- AbstractHandlerExceptionResolver:這個(gè)的子類比較多:
- SimpleMappingExceptionResolver:通過(guò)提前配置好的異常類和 View 之間的對(duì)應(yīng)關(guān)系來(lái)解析異常。
- AbstractHandlerMethodExceptionResolver:處理使用 @ExceptionHandler 注解自定義的異常類型。
- DefaultHandlerExceptionResolver:按照不同類型來(lái)處理異常。
- ResponseStatusExceptionResolver:處理含有 @ResponseStatus 注解的異常。
在 SpringMVC 中,大致的異常解析器就是這些,接下來(lái)我們來(lái)逐個(gè)學(xué)習(xí)這些異常解析器。
2.AbstractHandlerExceptionResolver
AbstractHandlerExceptionResolver 是真正干活的異常解析器的父類,我們就先從他的 resolveException 方法開(kāi)始看起。
- @Override
- @Nullable
- public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
- if (shouldApplyTo(request, handler)) {
- prepareResponse(ex, response);
- ModelAndView result = doResolveException(request, response, handler, ex);
- if (result != null) {
- logException(ex, request);
- }
- return result;
- }
- else {
- return null;
- }
- }
- 首先調(diào)用 shouldApplyTo 方法判斷當(dāng)前解析器是否可以處理傳入的處理器所拋出的異常,如果不支持,則直接返回 null,這個(gè)異常將交給下一個(gè) HandlerExceptionResolver 去處理。
- 調(diào)用 prepareResponse 方法處理 response。
- 調(diào)用 doResolveException 方法實(shí)際處理異常,這是一個(gè)模版方法,具體的實(shí)現(xiàn)在子類中。
- 調(diào)用 logException 方法記錄異常日志信息。
記錄異常日志沒(méi)啥好說(shuō)的,doResolveException 則是一個(gè)空的模版方法,所以這里對(duì)我們來(lái)說(shuō)主要就是兩個(gè)方法:shouldApplyTo 和 prepareResponse,我們分別來(lái)看。
shouldApplyTo
- protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
- if (handler != null) {
- if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
- return true;
- }
- if (this.mappedHandlerClasses != null) {
- for (Class> handlerClass : this.mappedHandlerClasses) {
- if (handlerClass.isInstance(handler)) {
- return true;
- }
- }
- }
- }
- return !hasHandlerMappings();
- }
這里涉及到了兩個(gè)對(duì)象:mappedHandlers 和 mappedHandlerClasses:
- mappedHandlers:存儲(chǔ)的是處理器對(duì)象(Controller 或者 Controller 中的方法)
- mappedHandlerClasses:存儲(chǔ)的是處理器的 Class。
我們?cè)谂渲卯惓=馕銎鞯臅r(shí)候可以配置這兩個(gè)對(duì)象,進(jìn)而實(shí)現(xiàn)該異常處理器只為某一個(gè)處理器服務(wù),但是一般來(lái)說(shuō)沒(méi)這種需求,所以大家僅做了解即可。
如果開(kāi)發(fā)者一開(kāi)始配置了 mappedHandlers 或者 mappedHandlerClasses,則用這兩個(gè)和處理器去比較,否則就直接返回 true,表示支持該異常處理。
prepareResponse
prepareResponse 方法比較簡(jiǎn)單,主要是處理一下響應(yīng)頭的緩存字段。
- protected void prepareResponse(Exception ex, HttpServletResponse response) {
- if (this.preventResponseCaching) {
- preventCaching(response);
- }
- }
- protected void preventCaching(HttpServletResponse response) {
- response.addHeader(HEADER_CACHE_CONTROL, "no-store");
- }
這是 AbstractHandlerExceptionResolver 的大致內(nèi)容,可以看到還是非常 easy 的,接下來(lái)我們來(lái)看它的實(shí)現(xiàn)類。
2.1 AbstractHandlerMethodExceptionResolver
AbstractHandlerMethodExceptionResolver 主要是重寫(xiě)了 shouldApplyTo 方法和 doResolveException 方法,一個(gè)一個(gè)來(lái)看。
shouldApplyTo
- @Override
- protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
- if (handler == null) {
- return super.shouldApplyTo(request, null);
- }
- else if (handler instanceof HandlerMethod) {
- HandlerMethod handlerMethod = (HandlerMethod) handler;
- handler = handlerMethod.getBean();
- return super.shouldApplyTo(request, handler);
- }
- else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) {
- return super.shouldApplyTo(request, handler);
- }
- else {
- return false;
- }
- }
這塊感覺(jué)沒(méi)啥好說(shuō)的,判斷邏輯基本上都還是調(diào)用父類的 shouldApplyTo 方法去處理。
doResolveException
- @Override
- @Nullable
- protected final ModelAndView doResolveException(
- HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
- HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
- return doResolveHandlerMethodException(request, response, handlerMethod, ex);
- }
- @Nullable
- protected abstract ModelAndView doResolveHandlerMethodException(
- HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
doResolveException 是具體的異常處理方法,但是它里邊卻沒(méi)有實(shí)質(zhì)性操作,具體的事情交給 doResolveHandlerMethodException 方法去做了,而該方法是一個(gè)抽象方法,具體的實(shí)現(xiàn)在子類中。
2.1.1 ExceptionHandlerExceptionResolver
AbstractHandlerMethodExceptionResolver 只有一個(gè)子類就是 ExceptionHandlerExceptionResolver,來(lái)看下它的 doResolveHandlerMethodException 方法:
- @Override
- @Nullable
- protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
- HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
- ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
- if (exceptionHandlerMethod == null) {
- return null;
- }
- if (this.argumentResolvers != null) {
- exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
- }
- if (this.returnValueHandlers != null) {
- exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
- }
- ServletWebRequest webRequest = new ServletWebRequest(request, response);
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- ArrayList
exceptions = new ArrayList<>(); - try {
- if (logger.isDebugEnabled()) {
- logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
- }
- // Expose causes as provided arguments as well
- Throwable exToExpose = exception;
- while (exToExpose != null) {
- exceptions.add(exToExpose);
- Throwable cause = exToExpose.getCause();
- exToExpose = (cause != exToExpose ? cause : null);
- }
- Object[] arguments = new Object[exceptions.size() + 1];
- exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
- arguments[arguments.length - 1] = handlerMethod;
- exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
- }
- catch (Throwable invocationEx) {
- // Any other than the original exception (or a cause) is unintended here,
- // probably an accident (e.g. failed assertion or the like).
- if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
- logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
- }
- // Continue with default processing of the original exception...
- return null;
- }
- if (mavContainer.isRequestHandled()) {
- return new ModelAndView();
- }
- else {
- ModelMap model = mavContainer.getModel();
- HttpStatus status = mavContainer.getStatus();
- ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
- mav.setViewName(mavContainer.getViewName());
- if (!mavContainer.isViewReference()) {
- mav.setView((View) mavContainer.getView());
- }
- if (model instanceof RedirectAttributes) {
- Map
flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); - RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
- }
- return mav;
- }
- }
這個(gè)方法雖然比較長(zhǎng),但是很好理解:
- 首先查找到帶有 @ExceptionHandler 注解的方法,封裝成一個(gè) ServletInvocableHandlerMethod 對(duì)象(關(guān)于 ServletInvocableHandlerMethod 對(duì)象,松哥在之前的文章中已經(jīng)介紹過(guò)了,具體參見(jiàn):Spring Boot 定義接口的方法是否可以聲明為 private?)。
- 如果找到了對(duì)應(yīng)的方法,則為 exceptionHandlerMethod 配置參數(shù)解析器、視圖解析器等,關(guān)于這些解析器,參考松哥之前的文章:SpringBoot 中如何自定義參數(shù)解析器?、深入分析 SpringMVC 參數(shù)解析器、Spring Boot 中如何統(tǒng)一 API 接口響應(yīng)格式?。
- 接下來(lái)定義一個(gè) exceptions 數(shù)組,如果發(fā)生的異常存在異常鏈,則將整個(gè)異常鏈存入 exceptions 數(shù)組中。
- exceptions 數(shù)組再加上 handlerMethod,共同組成方法參數(shù),調(diào)用 exceptionHandlerMethod.invokeAndHandle 完成自定義異常方法的執(zhí)行,執(zhí)行結(jié)果被保存再 mavContainer 中。
- 如果請(qǐng)求到此結(jié)束,則直接構(gòu)造一個(gè) ModelAndView 返回。
- 否則從 mavContainer 中取出各項(xiàng)信息,構(gòu)建新的 ModelAndView 返回。同時(shí),如果存在重定向參數(shù),也將之保存下來(lái)(關(guān)于重定向參數(shù),參見(jiàn):SpringMVC 中的參數(shù)還能這么傳遞?漲姿勢(shì)了!)。
這就是 ExceptionHandlerExceptionResolver 的大致工作流程,可以看到,還是非常 easy 的。
2.2 DefaultHandlerExceptionResolver
這個(gè)看名字就知道是一個(gè)默認(rèn)的異常處理器,用來(lái)處理一些常見(jiàn)的異常類型,我們來(lái)看一下它的 doResolveException 方法:
- @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) {
- }
- return null;
- }
可以看到,這里實(shí)際上就是根據(jù)不同的異常類型,然后調(diào)用不同的類去處理該異常。這里相關(guān)的處理都比較容易,以 HttpRequestMethodNotSupportedException 為例,異常處理就是對(duì) response 對(duì)象做一些配置,如下:
- 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, ", "));
- }
- response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
- return new ModelAndView();
- }
配置響應(yīng)頭,然后 sendError,最后返回一個(gè)空的 ModelAndView 對(duì)象。
其實(shí)這里哥哥異常處理方法都大同小異,松哥就不再贅述啦。
2.3 ResponseStatusExceptionResolver
這個(gè)用來(lái)處理 ResponseStatusException 類型的異常,或者使用了 @ResponseStatus 注解標(biāo)記的普通異常類。我們來(lái)看下它的 doResolveException 方法:
- @Override
- @Nullable
- protected ModelAndView doResolveException(
- HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
- try {
- if (ex instanceof ResponseStatusException) {
- return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
- }
- ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
- if (status != null) {
- return resolveResponseStatus(status, request, response, handler, ex);
- }
- if (ex.getCause() instanceof Exception) {
- return doResolveException(request, response, handler, (Exception) ex.getCause());
- }
- }
- catch (Exception resolveEx) {
- }
- return null;
- }
可以看到,首先判斷異常類型是不是 ResponseStatusException,如果是,則直接調(diào)用 resolveResponseStatusException 方法進(jìn)行異常信息處理,如果不是,則去查找到異常類上的 @ResponseStatus 注解,并從中查找出相關(guān)的異常信息,然后調(diào)用 resolveResponseStatus 方法進(jìn)行處理。
可以看到,ResponseStatusExceptionResolver 處理的異常類型有兩種:
- 直接繼承自 ResponseStatusException 的異常類,這種異常類可以直接從里邊提取出來(lái)想要的信息。
- 通過(guò) @ResponseStatus 注解的普通異常類,這種情況下異常信息從 @ResponseStatus 注解中提取出來(lái)。
這個(gè)比較簡(jiǎn)單,沒(méi)啥好說(shuō)的。
2.4 SimpleMappingExceptionResolver
SimpleMappingExceptionResolver 則是根據(jù)不同的異常顯示不同的 error 頁(yè)面??赡苡械男』锇檫€沒(méi)用過(guò) SimpleMappingExceptionResolver,所以松哥這里先簡(jiǎn)單說(shuō)一下用法。
SimpleMappingExceptionResolver 的配置非常簡(jiǎn)單,直接提供一個(gè) SimpleMappingExceptionResolver 的實(shí)例即可,如下:
- @Bean
- SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
- SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
- Properties mappings = new Properties();
- mappings.put("java.lang.ArithmeticException", "11");
- mappings.put("java.lang.NullPointerException", "22");
- resolver.setExceptionMappings(mappings);
- Properties statusCodes = new Properties();
- statusCodes.put("11", "500");
- statusCodes.put("22", "500");
- resolver.setStatusCodes(statusCodes);
- return resolver;
- }
在 mappings 中配置異常和 view 之間的對(duì)應(yīng)關(guān)系,要寫(xiě)異常類的全路徑,后面的 11、22 則表示視圖名稱;statusCodes 中配置了視圖和響應(yīng)狀態(tài)碼之間的映射關(guān)系。配置完成后,如果我們的項(xiàng)目在運(yùn)行時(shí)拋出了 ArithmeticException 異常,則會(huì)展示出 11 視圖,如果我們的項(xiàng)目在運(yùn)行時(shí)拋出了 NullPointerException 異常,則會(huì)展示出 22 視圖。
這是用法,了解了用法之后我們?cè)賮?lái)看源碼,就容易理解了,我們直接來(lái)看 doResolveException 方法:
- @Override
- @Nullable
- protected ModelAndView doResolveException(
- HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
- String viewName = determineViewName(ex, request);
- if (viewName != null) {
- Integer statusCode = determineStatusCode(request, viewName);
- if (statusCode != null) {
- applyStatusCodeIfPossible(request, response, statusCode);
- }
- return getModelAndView(viewName, ex, request);
- }
- else {
- return null;
- }
- }
- 首先調(diào)用 determineViewName 方法確定視圖的名稱。
- 接下來(lái)調(diào)用 determineStatusCode 查看視圖是否有對(duì)應(yīng)的 statusCode。
- 調(diào)用 applyStatusCodeIfPossible 方法將 statusCode 設(shè)置到 response 上,這個(gè)方法很簡(jiǎn)單,不多說(shuō)。
- 調(diào)用 getModelAndView 方法構(gòu)造一個(gè) ModelAndView 對(duì)象返回,在構(gòu)造時(shí),同時(shí)設(shè)置異常參數(shù),異常的信息的 key 默認(rèn)就是 exception。
在上面這個(gè)過(guò)程中,有兩個(gè)比較長(zhǎng)的方法,松哥這里需要和大家額外多說(shuō)兩句。
determineViewName
這個(gè)就是根據(jù)異常類型找到視圖名,我們來(lái)看下具體的查找方式:
- @Nullable
- protected String determineViewName(Exception ex, HttpServletRequest request) {
- String viewName = null;
- if (this.excludedExceptions != null) {
- for (Class> excludedEx : this.excludedExceptions) {
- if (excludedEx.equals(ex.getClass())) {
- return null;
- }
- }
- }
- if (this.exceptionMappings != null) {
- viewName = findMatchingViewName(this.exceptionMappings, ex);
- }
- if (viewName == null && this.defaultErrorView != null) {
- viewName = this.defaultErrorView;
- }
- return viewName;
- }
- 如果當(dāng)前異常包含在 excludedExceptions 中,則直接返回 null(意思是當(dāng)前異常被忽略處理了,直接按照默認(rèn)方式來(lái))。
- 如果 exceptionMappings 不為 null,則直接調(diào)用 findMatchingViewName 方法查找異常對(duì)應(yīng)的視圖名(exceptionMappings 變量就是前面我們配置的映射關(guān)系),具體的查找方式就是遍歷我們前面配置的映射表。
- 如果沒(méi)找到對(duì)應(yīng)的 viewName,并且用戶配置了 defaultErrorView,則將 defaultErrorView 賦值給 viewName,并將 viewName 返回。
determineStatusCode
- @Nullable
- protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
- if (this.statusCodes.containsKey(viewName)) {
- return this.statusCodes.get(viewName);
- }
- return this.defaultStatusCode;
- }
這個(gè)就比較容易,直接去 statusCodes 中查看是否有視圖對(duì)應(yīng)的狀態(tài)碼,如果有則直接返回,如果沒(méi)有,就返回一個(gè)默認(rèn)的。
3.HandlerExceptionResolverComposite
最后,還有一個(gè) HandlerExceptionResolverComposite 需要和大家介紹下,這是一個(gè)組合的異常處理器,用來(lái)代理哪些真正干活的異常處理器。
- @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;
- }
它的 resolveException 方法就比較簡(jiǎn)單了,這種寫(xiě)法我們已經(jīng)見(jiàn)到過(guò)很多次了,不再贅述。
4.小結(jié)
好啦,今天就和大家簡(jiǎn)單聊一聊 SpringMVC 中的異常處理體系,整體來(lái)說(shuō)并不難,小伙伴們可以仔細(xì)品一品。
本文轉(zhuǎn)載自微信公眾號(hào)「江南一點(diǎn)雨」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系江南一點(diǎn)雨公眾號(hào)。
名稱欄目:SpringMVC異常處理體系深入分析
轉(zhuǎn)載來(lái)源:http://m.fisionsoft.com.cn/article/coiesio.html


咨詢
建站咨詢
