SpringMVC之异常处理机制

Posted 敲代码的小小酥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringMVC之异常处理机制相关的知识,希望对你有一定的参考价值。

HandlerExceptionResolver接口

该接口是SpringMVC处理异常的祖接口。下面我们看该类的注释:

接口由对象实现,这些对象可以解决在处理程序映射或执行期间抛出的异常,在典型情况下是错误视图。实现者通常在应用程序上下文中注册为bean。
错误视图类似于JSP错误页面,但可以用于任何类型的异常,包括任何已检查的异常,以及特定处理程序的潜在细粒度映射。

该接口只有一个方法:

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

看其注释:

尝试解决在处理程序执行期间引发的给定异常,如果合适,返回表示特定错误页的ModelAndView。
返回的ModelAndView可能为空,表示异常已成功解决,但不应呈现任何视图,例如通过设置状态代码。

可见,该接口设计的初衷是当发生异常时,返回异常界面视图。

AbstractHandlerExceptionResolver抽象实现类

两个重要属性:

    @Nullable
	private Set<?> mappedHandlers;

	@Nullable
	private Class<?>[] mappedHandlerClasses;

这两个属性用于存放出现异常的handler对象。这两个集合包含的handler出现异常后,才会进行异常处理。

抽象方法:

@Nullable
	protected abstract ModelAndView doResolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

该方法由子类实现,来实现具体的异常处理逻辑。是个钩子方法。

接口定义的resolveException方法实现:

@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) {
				// 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);
			}
			return result;
		}
		else {
			return null;
		}
	}

由代码可知,只要是判断两个Set集合是否包含发生异常的handler对象,如果包含,则调用doResolveException方法处理异常,如果没有包含,则返回null。
综上所述,AbstractHandlerExceptionResolver抽象类主要的职责是判断发生异常的handler是否需要走异常处理机制。

SimpleMappingExceptionResolver实现类

顾名思义,该类是最简单的一个异常处理实现类。核心逻辑就是根据不同的异常类型,返回到不同的ModelAndView视图上。下面看其源码:

	@Nullable
	private Properties exceptionMappings;

	@Nullable
	private Class<?>[] excludedExceptions;

上面两个属性存放异常对应异常与视图的映射关系以及排除的异常类型。可见是在按异常类型进行异常视图的匹配。

看核心方法doResolveException方法,即上述抽象类的抽象方法:

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;
		}
	}

看几个关键步骤:

String viewName = determineViewName(ex, request);

根据异常获得该异常对应的视图。如果没有视图,则直接返回null。

Integer statusCode = determineStatusCode(request, viewName);
			if (statusCode != null) {
				applyStatusCodeIfPossible(request, response, statusCode);
			}

获取request的状态码,并传递给response对象。

return getModelAndView(viewName, ex, request);

最后,返回ModelAndView错误视图。

由上面的逻辑也可以看出,该类为何Simple了。

DefaultHandlerExceptionResolver实现类

SpringMVC默认使用的这个类作为异常处理机制,下面我们看其核心方法doResolveException方法:

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;
	}

这个方法使用大量的if…else…语句进行异常类型的判断,不同的异常类型,又调用了各自异常的处理方法。很明显,这段代码可以使用策略模式进行优化。
下面,我们看其中一个异常的处理方法,其他异常的套路都是一样的:

protected ModelAndView handleMissingServletRequestPartException(MissingServletRequestPartException ex,
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {

		response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
		return new ModelAndView();
	}

可以看到,对于异常的处理方式就是将错误编号传给了response对象,然后返回了一个新建的ModelAndView对象。所有的异常都是这种处理方式。这就是如果我们使用默认错误处理机制,项目报错时,浏览器直接显示错误编号的原因。为了更好的用户体验,一般不用默认的异常处理机制。

ResponseStatusExceptionResolver实现类

@ResponseStatus注解对应的异常类,该类没什么可讲的,知道@ResponseStatus作用即可。

AbstractHandlerMethodExceptionResolver抽象类

该类是AbstractHandlerExceptionResolver抽象类的子抽象类,用于处理HandlerMethod类型的handler抛出的异常。
我们先看其匹配handler方法:

可以看到,如果handler是HandlerMethod类型,则获取到HandlerMethod所属的bean,然后调用其父抽象类AbstractHandlerExceptionResolver的shouldApplyTo方法,走父类的判断逻辑。所以归根结底还是根据bean,进行的异常机制的匹配。
定义的抽象方法如下:

protected abstract ModelAndView doResolveHandlerMethodException(
			HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);

定义了针对HandlerMethod类型的handler抛出异常的处理方案。

ExceptionHandlerExceptionResolver实现类

参考:ExceptionHandlerExceptionResolver类源码解析

以上是关于SpringMVC之异常处理机制的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC学习—项目统一异常处理机制详解与使用案例

SpringMVC 异常处理机制,

SpringMVC 异常处理机制,

异常和TCP通讯

SpringMVC——拦截器异常处理机制

开发之统一异常处理