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