Bootstrap

java源码 - SpringMVC(7)之 HandlerExceptionResolver

1. RequestToViewNameTranslator

RequestToViewNameTranslator可以在处理器返回的view为空时使用它根据request获取viewName。
在SpringMVC中只有一个唯一的实现:

/ * *
 * {@link RequestToViewNameTranslator}这只是转换的URI
*传入的请求到一个视图名称。
可以显式定义为a中的{@code viewNameTranslator} bean
* {@link org.springframework.web.servlet。DispatcherServlet}上下文。
否则,将使用普通的默认实例。
默认转换只是去除前导斜杠和后导斜杠
*以及URI的文件扩展名,并返回结果
*使用已配置的{@link #setPrefix}和a查看名称
* {@link #setSuffix后缀}添加的适当。
*
可以禁用前导斜杠和文件扩展名的删除
*使用{@link #setStripLeadingSlash}* {@link #setStripExtension stripExtension}属性。
 *
下面是一些请求查看名称翻译的例子。
* 
* {@code http://localhost: 8080 / gamecast /display.html} {@code显示}< / >
* {@code http://localhost: 8080 / gamecast / displayShoppingCart。html} {@code displayShoppingCart} < / >
* {@code http://localhost: 8080 / gamecast / admin /index.html} {@code admin /index}< / >
 * @see org.springframework.web.servlet.RequestToViewNameTranslator
 * @see org.springframework.web.servlet.ViewResolver
 */
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {

2. HandlerExceptionResolver

HandlerExceptionResolver用于解析请求处理过程中所产生的异常。

其内只有一个方法:

	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

依赖关系如下:
在这里插入图片描述
其中HandlerExceptionResolverComposite作为容器使用,可以封装别的Resolver。
HandlerExceptionResolver的主要实现都继承自抽象类:AbstractHandlerExceptionResolver:
在这里插入图片描述

  • AbstractHandlerMethodExceptionResolver:和其子类 - -
  • ExceptionHandlerExceptionResolver一起完成使用@ExceptionHandler注释的方法进行异常解析的功能。
  • DefaultHandlerExceptionResolver:按不同类型分别对异常进行解析。
    ResponseStatusExceptionResolver:解析有@ResponseStatus注释类型的异常。
  • SimpleMappingExceptionResolver:通过配置的异常类和view的对应关系来解析异常。

异常解析过程主要包含两部分内容:给ModelAndView设置相应内容、设置response的相关属性。

3. AbstractHandlerExceptionResolver

AbstractHandlerExceptionResolver是所有直接解析异常类的父类,里面定义了通用的解析流程,并使用了模板模式,子类只需要覆盖相应的方法即可:

	/ * *
	*检查这个解析器是否应该应用(例如,如果提供的处理程序
	*匹配任何已配置的{@linkplain #setMappedHandlers}* {@linkplain #setMappedHandlerClasses handler classes}),然后委托
	*{@link #doResolveException}模板方法。
	* /
	@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) {
				//当警告记录器未启用时打印调试消息。
				if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
					logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
				}
				//在logException方法中显式配置警告日志程序。
				logException(ex, request);
			}
			return result;
		}
		else {
			return null;
		}
	}

首先使用shouldApplyTo方法判断当前ExceptionResolver是否可以解析所传入处理器所抛出的异常。接着调用prepareResponse设置response。最后调用doResolveException实际解析异常。

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;
					}
				}
			}
		}
		//Else仅适用于没有显式处理程序映射的情况。
		return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
	}

mappedHandlers和mappedHandlerClasses,这两个属性可以在定义HandlerExceptionResolver的时候进行配置,用于指定可以解析处理器抛出的哪些异常,也就是如果设置了这两个值中的一个,那么这个ExceptionResolver就只能解析所设置的处理器抛出的异常。mappedHandlers用于配置处理器的集合,mappedHandlerClasses用于配置处理器类型的集合。

prepareResponse方法:

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

prepareResponse方法根据preventResponseCaching标示判断是否给response设置禁用缓存的属性,preventResponseCaching默认为false。

3.1 ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver继承自AbstractHandlerMethodExceptionResolver。

执行使用的ServletInvocableHandlerMethod,首先根据handlerMethod和exception将其创建出来(大致过程是在处理器类里找出所有注释了@ExceptionHandler的方法,然后再根据其配置中的异常和需要解析的异常进行匹配),然后设置了argumentResolvers和returnValueHandlers,接着调用其invokeAndHandle方法执行处理,最后将处理结果封装成ModelAndView返回。

	/**
	*找到一个{@code @ExceptionHandler}方法并调用它来处理抛出的异常。
	 */
	@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();

		try {
			if (logger.isDebugEnabled()) {
				logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
			}
			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) {
			//除了最初的异常(或其原因)之外,其他任何异常在这里都是无意的,
			//可能是意外(例如断言失败或类似)。
			if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
				logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
			}
			//继续原始异常的默认处理…
			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<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
			return mav;
		}
	}

3.2 DefaultHandlerExceptionResolver

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) {
			if (logger.isWarnEnabled()) {
				logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
			}
		}
		return null;
	}

具体的解析方法也非常简单,主要是设置response的相关属性。
主要处理一些不支持的方法并设置一些返回情况。

3.3 ResponseStatusExceptionResolver

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) {
			if (logger.isWarnEnabled()) {
				logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
			}
		}
		return null;
	}

doResolveException方法中首先使用AnnotationUtils找到ResponseStatus注释,然后调用resolveResponseStatus方法进行解析,后者使用注释里的value和reason作为参数调用了response的sendError方法。

3.4 SimpleMappingExceptionResolver

	/**
	*实际解决在处理程序执行过程中抛出的给定异常,
	返回一个ModelAndView,表示一个特定的错误页面。
	为了应用特定的异常检查,可以在子类中重写>。
	*注意这个模板方法将在检查是否这样后被调用
	*解决应用("mappedHandlers"等),所以实现可以简单地继续
	带有实际的异常处理。
	* @param请求当前HTTP请求
	* @param响应当前HTTP响应
	* @param处理程序,或{@code null},如果当时没有选择
	*异常(例如,多部分解析失败)
	* @param ex处理程序执行期间抛出的异常
	返回一个对应的{@code ModelAndView}来转发,
	*或{@code null}用于解析链中的默认处理
	 */
	@Override
	@Nullable
	protected ModelAndView doResolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		// Expose ModelAndView for chosen error view.
		String viewName = determineViewName(ex, request);
		if (viewName != null) {
			// Apply HTTP status code for error views, if specified.
			// Only apply it if we're processing a top-level request.
			Integer statusCode = determineStatusCode(request, viewName);
			if (statusCode != null) {
				applyStatusCodeIfPossible(request, response, statusCode);
			}
			return getModelAndView(viewName, ex, request);
		}
		else {
			return null;
		}
	}

这里首先调用determineViewName方法根据异常找到显示异常的逻辑视图,然后调用determineStatusCode方法判断逻辑视图是否有对应的statusCode,如果有则调用applyStatus-CodeIfPossible方法设置到response,最后调用getModelAndView将异常和解析出的viewName封装成ModelAndView并返回。
SimpleMappingExceptionResolver需要提前配置异常类和view的对应关系然后才能使用。

/ * *
*确定给定异常的视图名,首先检查
* {@link # setExcludedExceptions([])“excludedExecptions”},然后搜索
* {@link #setExceptionMappings "},最后使用
* {@link #setDefaultErrorView "defaultErrorView"}作为回退。
* @param ex处理程序执行期间抛出的异常
* @param请求当前HTTP请求(对获取元数据很有用)
* @返回解析后的视图名称,或者如果没有找到,返回{@code null}
* /
	@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);
		}
		/如果定义了,返回默认错误视图elseif (viewName == null && this.defaultErrorView != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Resolving to default view '" + this.defaultErrorView + "'");
			}
			viewName = this.defaultErrorView;
		}
		return viewName;
	}

首先检查异常是不是配置在excludedExceptions中,如果是则返回null,否则调用findMatchingViewName实际查找viewName,如果没找到而且配置了defaultErrorView,则使用defaultErrorView。
findMatchingViewName从传入的参数就可以看出来它是根据配置的exceptionMappings参数匹配当前异常的,不过并不是直接完全匹配的,而是只要配置异常的字符在当前处理的异常或其父类中存在就可以了。

	/**
	* 在给定的异常映射中找到匹配的视图名称。
	异常类名和错误视图名之间的映射
	* @param ex处理程序执行期间抛出的异常
	* @返回视图名称,如果没有找到,则返回{@code null}
	* @see # setExceptionMappings
	*/
	@Nullable
	protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
		String viewName = null;
		String dominantMapping = null;
		int deepest = Integer.MAX_VALUE;
		for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
			String exceptionMapping = (String) names.nextElement();
			int depth = getDepth(exceptionMapping, ex);
			if (depth >= 0 && (depth < deepest || (depth == deepest &&
					dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) {
				deepest = depth;
				dominantMapping = exceptionMapping;
				viewName = exceptionMappings.getProperty(exceptionMapping);
			}
		}
		if (viewName != null && logger.isDebugEnabled()) {
			logger.debug("Resolving to view '" + viewName + "' based on mapping [" + dominantMapping + "]");
		}
		return viewName;
	}

后面的方法就慢慢的点进去看吧。

4. 总结:

mvc:annotation-driven会自动将ExceptionHandlerExceptionResolver、DefaultHandlerException-Resolver和ResponseStatusExceptionResolver配置到SpringMVC中,SimpleMappingException-Resolver如果想使用需要自己配置,其实SimpleMappingExceptionResolver的使用还是很方便的,只需要将异常类型和错误页面的对应关系设置进去就可以了,而且还可以通过设置父类将某种类型的所有异常对应到指定页面。另外ExceptionHandlerExceptionResolver不仅可以使用处理器类中注释的@ExceptionHandler方法处理异常,还可以使用@ControllerAdvice注释的类里有@ExceptionHandler注释的全局异常处理方法。

但是异常处理本身所抛出的异常和视图解析过程中抛出的异常它是不能处理的。

;