SpringMVC源碼情操陶冶-AbstractHandlerExceptionResolver


springmvc支持服務端在處理業務邏輯過程中出現異常的時候可以配置相應的ModelAndView對象返回給客戶端,本文介紹springmvc默認的幾種HandlerExceptionResolver類

實際應用

springmvc的xml配置化-Exception配置

<bean id="exceptionHandler" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<!--設置默認返回viewName,通常與freemarker引擎搭配使用-->
	<property name="defaultErrorView" value="error/defaultError" />
	<!--設置默認返回response status-->
	<property name="defaultStatusCode" value="500" />
	
	<!--配置相應的異常類與viewName的映射-->
	<property name="exceptionMappings">
        <props>
            <prop key="SessionTimeoutException">redirect:../login.html</prop>
			<prop key="AuthenticationException">error/403</prop>
        </props>
	</property>
</bean>

以上的配置會對出SessionTimeoutException異常則跳轉至login頁面,對AuthenticationException異常則跳轉至403頁面,對其他的異常則默認跳轉至defaultError頁面呈現並返回500的錯誤碼

HandlerExceptionResolver-異常解析接口

接口內只有一個方法resolveException(),通過解析異常查詢配置以得到符合條件的ModelAndView對象

	/**
	 * Try to resolve the given exception that got thrown during handler execution,
	 * returning a {@link ModelAndView} that represents a specific error page if appropriate.
	 * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
	 * to indicate that the exception has been resolved successfully but that no view
	 * should be rendered, for instance by setting a status code.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler the executed handler, or {@code null} if none chosen at the
	 * time of the exception (for example, if multipart resolution failed)
	 * @param ex the exception that got thrown during handler execution
	 * @return a corresponding {@code ModelAndView} to forward to, or {@code null}
	 * for default processing
	 */
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

AbstractHandlerExceptionResolver-異常解析抽象基類

所有的spring內置異常解析類都繼承於此,直奔主題看resolveException()方法

	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
			Object handler, Exception ex) {
		//判斷是否需要解析
		if (shouldApplyTo(request, handler)) {
			//此處一般是判斷內部屬性preventResponseCaching是否為true,是則設置響應包頭cache-control:no-store
			prepareResponse(ex, response);
			//使用模板方法doResolveException()方法供子類實現
			ModelAndView result = doResolveException(request, response, handler, ex);
			if (result != null) {
				//日志打印一發
				logException(ex, request);
			}
			return result;
		}
		else {
			return null;
		}
	}

附帶着分析下shouldApplyTo()方法

	/**
	**可以配置mappedHandlers和mappedHandlerClasses屬性來特定匹配
	**默認情況下兩者都為空則直接返回true,表明對所有的handler都進行異常解析
	*/
	protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
		//此處的handler一般為bean對象
		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;
					}
				}
			}
		}
		// Else only apply if there are no explicit handler mappings.
		return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
	}

1. SimpleMappingExceptionResolver-異常映射實現類

比較簡單的實現類,可以配綁定viewName和exception以完成簡單的異常映射視圖頁面

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

		// Expose ModelAndView for chosen error view.
		String viewName = determineViewName(ex, request);
		if (viewName != null) {
			//如果配置了statusCodes屬性,則對此異常的狀態碼進行設置
			Integer statusCode = determineStatusCode(request, viewName);
			if (statusCode != null) {
				applyStatusCodeIfPossible(request, response, statusCode);
			}
			//創建ModelAndView對象
			return getModelAndView(viewName, ex, request);
		}
		else {
			return null;
		}
	}

針對以上的源碼我們分兩步去簡單分析下

SimpleMappingExceptionResolver#determineViewName()-找尋viewName

	protected String determineViewName(Exception ex, HttpServletRequest request) {
		String viewName = null;
		//判斷異常是否屬於excludeExceptions集合內,是則直接返回null
		if (this.excludedExceptions != null) {
			for (Class<?> excludedEx : this.excludedExceptions) {
				if (excludedEx.equals(ex.getClass())) {
					return null;
				}
			}
		}
		// Check for specific exception mappings.
		// 從exceptionMappings集合內根據exception獲取到相應的viewName
		if (this.exceptionMappings != null) {
			viewName = findMatchingViewName(this.exceptionMappings, ex);
		}
		//當exceptionMappings集合內不存在指定的exception但是默認視圖指定則直接返回默認視圖
		if (viewName == null && this.defaultErrorView != null) {
			viewName = this.defaultErrorView;
		}
		return viewName;
	}
  • excludedExceptions集合可以過濾指定的exception,對其不進行解析直接返回null。可配置

  • exceptionMappings綁定了exception與viewName的關系,如果在其集合內沒找到相應的viewName,但是defaultErrorView屬性指定,則會直接返回defaultErrorView對應的視圖

SimpleMappingExceptionResolver#getModelAndView()-創建ModelAndView對象

	protected ModelAndView getModelAndView(String viewName, Exception ex) {
		ModelAndView mv = new ModelAndView(viewName);
		//exceptionAttribute默認為exception
		if (this.exceptionAttribute != null) {
			//將exception信息添加到model中
			mv.addObject(this.exceptionAttribute, ex);
		}
		return mv;
	}

主要就是將exceptionAttribute對應的參數值默認為exception屬性添加到視圖對象的model中

2. ResponseStatusExceptionResolver-響應狀態異常解析類

主要是解析帶有@ResponseStatus的異常類,將其中的異常信息描述直接返回給客戶端

	@Override
	protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
			Object handler, Exception ex) {
		//獲取相應類上的注解@ResponseStatus
		ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
		if (responseStatus != null) {
			try {
				return resolveResponseStatus(responseStatus, request, response, handler, ex);
			}
			catch (Exception resolveEx) {
				logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
			}
		}
		else if (ex.getCause() instanceof Exception) {
			ex = (Exception) ex.getCause();
			//遞歸
			return doResolveException(request, response, handler, ex);
		}
		return null;
	}

ResponseStatusExceptionResolver#resolveResponseStatus()-返回異常信息給客戶端

讀取@ResponseStatus注解信息,返回異常內容給客戶端

	protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
			HttpServletResponse response, Object handler, Exception ex) throws Exception {
		//狀態碼
		int statusCode = responseStatus.code().value();
		//異常原因描述
		String reason = responseStatus.reason();
		if (this.messageSource != null) {
			reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale());
		}
		//通過response對象直接返回錯誤信息給客戶端
		if (!StringUtils.hasLength(reason)) {
			response.sendError(statusCode);
		}
		else {
			//通過response對象直接返回錯誤信息給客戶端
			response.sendError(statusCode, reason);
		}
		return new ModelAndView();
	}

3. DefaultHandlerExceptionResolver-springmvc默認的異常解析處理

源碼就不公布了,讀者可自行去查詢,基本都是調用response的sendError()方法返回錯誤信息給客戶端。本文對其中的異常歸下類

  1. 請求方式異常
  • HttpRequestMethodNotSupportedException-服務端不支持相應的請求方法
  • HttpMediaTypeNotSupportedException/HttpMediaTypeNotAcceptableException-服務端/客戶端不支持相應的mediaType,比如application/json
  • MissingPathVariableException-@PathVaribale指定參數請求中不包含
  • MissingServletRequestParameterException/ServletRequestBindingException-請求參數綁定錯誤
  • MethodArgumentNotValidException-@Valid注解指定的參數校驗失敗
  • AsyncRequestTimeoutException-異步請求超時
  1. 消息內容異常
  • ConversionNotSupportedException-服務端找尋不到相應的Convert對象來解析javabean
  • TypeMismatchException-設置javabean屬性類型出錯
  • HttpMessageNotReadableException/HttpMessageNotWritableException-消息內容不可讀/不可寫
  • MissingServletRequestPartException-文件上傳類錯誤,可能請求沒有multipart/form-data或者服務不支持文件上傳
  • NoHandlerFoundException-handler處理器沒有找到,即可能沒有對應的請求處理供響應

4. ExceptionHandlerExceptionResolver-處理handlerMethod對象過程中的異常

具體的邏輯本文則不展開了,簡述下其中的邏輯:
當處理handlerMethod業務邏輯過程中出現了異常,則此解析器

  1. 嘗試從handlerMethod所在的class類去找尋是否含有@ExceptionHandler注解的方法

  2. 判斷@ExceptionHandler指定的exception類與產生的異常一致,一致則執行相應的方法,當有多個@ExceptionHandler(value),則默認采用第一個

  3. 當上述在class類找尋不到則嘗試判斷class類是否含有@ControllerAdvice注解,有則按照上述第一二步的步驟再次找尋@ControllerAdvice指定的類

小結

springmvc開放了對異常也可以包裝成頁面顯示的功能,通過本文的簡單分析可以幫助博主和讀者更好的理解springmvc對異常的處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM