HandlerExceptionResolver进行异常统一处理时,遇到无法捕获异常时的一种解决思路

1,001 阅读4分钟

jdk1.8,spring 4.3.9.RELEASE

springMVC项目中通过继承SimpleMappingExceptionResolver类实现类全局异常处理的功能。但一直存在一个问题,在某些情况下抛出的异常不能被自定义的异常解析器统一处理,比如rest接口的参数类型错误导致的TypeMismatchException。

原代码如下

public class ExceptionHandler extends SimpleMappingExceptionResolver {

	@Override
	protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
		
		String message = ex.getMessage();

		String forward = Constant.ERROR_URL;
        //都是自定义的异常
		JsonResponse jsonResponse = null;
		if (ex instanceof UnloginException) {
			jsonResponse = JsonResponse.unlogin(message);
			forward = Constant.LOGIN_URL;
		} else if (ex instanceof UnauthorizedException) {
			jsonResponse = JsonResponse.unauthorized(message);
		} else if (ex instanceof UnvalidatedException) {
			jsonResponse = JsonResponse.unvalidated(message);
		} else if (ex instanceof ForbiddenException) {
			jsonResponse = JsonResponse.forbidden(message);
		} else {
			message = "系统内部错误";
			jsonResponse = JsonResponse.serverInternalError(message);
		}

		if (UtilRequest.isAjaxRequest(request) || UtilRequest.isMultipartContent(request)) {
			writeJson(response, jsonResponse);
			return null;
		}

		return new ModelAndView(forward, "message", jsonResponse.getMessage());
	}

	private void writeJson(HttpServletResponse response, Object object) {
		response.setContentType("application/json;");
		PrintWriter pw = null;
		try {
			pw = response.getWriter();
			pw.write(UtilJson.toJson(object));
		} catch (IOException e) {
			logger.error(e);
		} finally {
			if (pw != null) {
				pw.close();
			}
		}
	}

}

一开始的思路是打算在filter中捕获抛出的异常,但是发现无论怎么样都无法获取的异常,后来看到了一篇文章:关于在filter中捕获Struts2异常方法说明,按照这个思路确实在request的attribute中发现到被处理后的异常"org.springframework.web.servlet.DispatcherServlet.EXCEPTION"。

于是顺着就去DispatcherServlet中找问题,在processHandlerException方法中找到了

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

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
		//这在一步会遍历所有的异常解析器并执行resolveException,如果返回不为空则跳出循环
		for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
			exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
			    //就在这一步处理了异常
				request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
				return null;
			}
			// We might still need view name translation for a plain error model...
			if (!exMv.hasView()) {
				exMv.setViewName(getDefaultViewName(request));
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

打断点,发现在遍历到DefaultHandlerExceptionResolver的时候就会跳出循环,于是继续进入DefaultHandlerExceptionResolver。

@Override
	@SuppressWarnings("deprecation")
	protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
			Object handler, Exception ex) {

		try {
			if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
				return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,
						request, response, handler);
			}
			else 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 handlerException) {
			if (logger.isWarnEnabled()) {
				logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
			}
		}
		return null;
	}

DefaultHandlerExceptionResolver确实会对包括TypeMismatchException在内的异常进行处理,而且在各自的处理方法中返回了一个new ModelAndView(),导致request.setAttribute(EXCEPTION_ATTRIBUTE, ex)这一句执行。

filter中捕获不到异常的原因找到了,不过并不是我要解决的问题。

在processHandlerException遍历this.handlerExceptionResolvers时,发现自定义的ExceptionHandler排在DefaultHandlerExceptionResolver后面,导致DefaultHandlerExceptionResolver处理了TypeMismatchException以后,直接跳出循环而不会执行ExceptionHandler。

继续顺着this.handlerExceptionResolvers,可以看到

private void initHandlerExceptionResolvers(ApplicationContext context) {
		this.handlerExceptionResolvers = null;

		if (this.detectAllHandlerExceptionResolvers) {
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
				// We keep HandlerExceptionResolvers in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
			}
		}
		else {
			try {
				HandlerExceptionResolver her =
						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
				this.handlerExceptionResolvers = Collections.singletonList(her);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, no HandlerExceptionResolver is fine too.
			}
		}

		// Ensure we have at least some HandlerExceptionResolvers, by registering
		// default HandlerExceptionResolvers if no other resolvers are found.
		if (this.handlerExceptionResolvers == null) {
			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}

initHandlerExceptionResolvers通过AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers)怼handlerExceptionResolvers进行了排序,那么问题就简单了(其实我还是花了好几个小时看)。

public class AnnotationAwareOrderComparator extends OrderComparator {

    ...
    ...

    protected Integer findOrder(Object obj) {
    	// Check for regular Ordered interface
    	Integer order = super.findOrder(obj);
    	if (order != null) {
    		return order;
    	}
    
    	// Check for @Order and @Priority on various kinds of elements
    	if (obj instanceof Class) {
    		return OrderUtils.getOrder((Class<?>) obj);
    	}
    	else if (obj instanceof Method) {
    		Order ann = AnnotationUtils.findAnnotation((Method) obj, Order.class);
    		if (ann != null) {
    			return ann.value();
    		}
    	}
    	else if (obj instanceof AnnotatedElement) {
    		Order ann = AnnotationUtils.getAnnotation((AnnotatedElement) obj, Order.class);
    		if (ann != null) {
    			return ann.value();
    		}
    	}
    	else if (obj != null) {
    		order = OrderUtils.getOrder(obj.getClass());
    		if (order == null && obj instanceof DecoratingProxy) {
    			order = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass());
    		}
    	}
    
    	return order;
    }
	
    ...
    ...

    public static void sort(List<?> list) {
    	if (list.size() > 1) {
            Collections.sort(list, INSTANCE);
        }
    }
	...
	...
}

可以看到AnnotationAwareOrderComparator继承了OrderComparator类,而AnnotationAwareOrderComparator.sort可以看到就是用Collections类进行排序。关于OrderComparator就不展开了。

于是ExceptionHandler改为

public class ExceptionHandler extends AbstractHandlerExceptionResolver{

    ...
    ...
    
    @Override
    public int getOrder() {
        return 1;
    }
    
    ...
    ...
}

打断点的时候看到DefaultHandlerExceptionResolver的order是2,所以这边吧order设置为1,问题解决。 至于继承的类,其实我不太清楚几个exceptionresolver的具体功能区别,用原来的应该也没问题。AbstractHandlerExceptionResolver和SimpleMappingExceptionResolver都实现了Ordered接口

就结果而言只是改了几句代码,但确实花了挺久时间才解决的,挺有成就感,所以特地注册了来写一篇文章。