SpringMVC 异常处理体系深入分析,温故而知新

在 SpringMVC 中,大致的异常解析器就是这些,接下来我们来逐个学习这些异常解析器。

2.AbstractHandlerExceptionResolver


AbstractHandlerExceptionResolver 是真正干活的异常解析器的父类,我们就先从他的 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) {

logException(ex, request);

}

return result;

}

else {

return null;

}

}

  1. 首先调用 shouldApplyTo 方法判断当前解析器是否可以处理传入的处理器所抛出的异常,如果不支持,则直接返回 null,这个异常将交给下一个 HandlerExceptionResolver 去处理。

  2. 调用 prepareResponse 方法处理 response。

  3. 调用 doResolveException 方法实际处理异常,这是一个模版方法,具体的实现在子类中。

  4. 调用 logException 方法记录异常日志信息。

记录异常日志没啥好说的,doResolveException 则是一个空的模版方法,所以这里对我们来说主要就是两个方法:shouldApplyTo 和 prepareResponse,我们分别来看。

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();

}

这里涉及到了两个对象:mappedHandlers 和 mappedHandlerClasses:

  • mappedHandlers:存储的是处理器对象(Controller 或者 Controller 中的方法)

  • mappedHandlerClasses:存储的是处理器的 Class。

我们在配置异常解析器的时候可以配置这两个对象,进而实现该异常处理器只为某一个处理器服务,但是一般来说没这种需求,所以大家仅做了解即可。

如果开发者一开始配置了 mappedHandlers 或者 mappedHandlerClasses,则用这两个和处理器去比较,否则就直接返回 true,表示支持该异常处理。

prepareResponse

prepareResponse 方法比较简单,主要是处理一下响应头的缓存字段。

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 的大致内容,可以看到还是非常 easy 的,接下来我们来看它的实现类。

2.1 AbstractHandlerMethodExceptionResolver

AbstractHandlerMethodExceptionResolver 主要是重写了 shouldApplyTo 方法和 doResolveException 方法,一个一个来看。

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;

}

}

这块感觉没啥好说的,判断逻辑基本上都还是调用父类的 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 是具体的异常处理方法,但是它里边却没有实质性操作,具体的事情交给 doResolveHandlerMethodException 方法去做了,而该方法是一个抽象方法,具体的实现在子类中。

2.1.1 ExceptionHandlerExceptionResolver

AbstractHandlerMethodExceptionResolver 只有一个子类就是 ExceptionHandlerExceptionResolver,来看下它的 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<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();

RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);

}

return mav;

}

}

这个方法虽然比较长,但是很好理解:

  1. 首先查找到带有 @ExceptionHandler 注解的方法,封装成一个 ServletInvocableHandlerMethod 对象(关于 ServletInvocableHandlerMethod 对象,松哥在之前的文章中已经介绍过了,具体参见:Spring Boot 定义接口的方法是否可以声明为 private?)。

  2. 如果找到了对应的方法,则为 exceptionHandlerMethod 配置参数解析器、视图解析器等,关于这些解析器,参考松哥之前的文章:SpringBoot 中如何自定义参数解析器?深入分析 SpringMVC 参数解析器Spring Boot 中如何统一 API 接口响应格式?

  3. 接下来定义一个 exceptions 数组,如果发生的异常存在异常链,则将整个异常链存入 exceptions 数组中。

  4. exceptions 数组再加上 handlerMethod,共同组成方法参数,调用 exceptionHandlerMethod.invokeAndHandle 完成自定义异常方法的执行,执行结果被保存再 mavContainer 中。

  5. 如果请求到此结束,则直接构造一个 ModelAndView 返回。

  6. 否则从 mavContainer 中取出各项信息,构建新的 ModelAndView 返回。同时,如果存在重定向参数,也将之保存下来(关于重定向参数,参见:SpringMVC 中的参数还能这么传递?涨姿势了!)。

这就是 ExceptionHandlerExceptionResolver 的大致工作流程,可以看到,还是非常 easy 的。

2.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);

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

, request, response, handler);

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-NeirfA45-1710892589694)]
[外链图片转存中…(img-CCAeSIFC-1710892589695)]
[外链图片转存中…(img-15uej8XB-1710892589695)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-2Woz38WR-1710892589696)]

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值