我们知道,Spring MVC中提供了处理异常的解析器(HandlerExceptionResolver),其意图是,当与请求匹配的控制器(Handler 或Controller)处理请求时,将发生的异常交由HandlerExceptionResolverde>来处理,从而给框架的使用者一个集中处理异常的机会。de>
Spring MVC提供了一个de>HandlerExceptionResolversde>de>的de>简单实现SimpleMappingExceptionResolver,这个解析器将抛出异常的类名映射到一个视图名,通过配置,可以将不同的异常映射到不同的错误视图。
当然,你也可以实现一个自己的HandlerExceptionResolver,只需实现
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法, 并返回一个ModelAndView。
2 Spring MVC 2.5.5问题描述
在做项目的过程中,我们发现Spring MVC 2.5.5的异常处理不是很好使,具体的表现就是,SimpleMappingExceptionResolver捕获了异常,并正确的将异常映射到视图(view),但是当视图渲染(render)完之后,结束了请求,浏览器中并没有显示相应的视图信息,而是熟悉的Tomcat 500页面,如下图:
图1
这里可以确定的是,JSP视图页面肯定执行了(你可以在JSP中添加测试代码如:System.out.println("ok"),查看Tomcat控制台是否会输出“ok”)。
3 深入剖析
经过跟踪Tomcat的源码,我们发现,Tomcat的Host Vale在响应请求时会检查请求是否包含了异常信息。代码org.apache.catalina.core.StandardHostValve.java片段如下:
// Error page processing response.setSuspended(false); // EXCEPTION_ATTR = "javax.servlet.error.exception"; Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);
if (t != null) { throwable(request, response, t); } else { status(request, response); }
protected void throwable(Request request, Response response, Throwable throwable) { Context context = request.getContext(); if (context == null) return;
Throwable realError = throwable;
if (realError instanceof ServletException) { realError = ((ServletException) realError).getRootCause(); if (realError == null) { realError = throwable; } }
// If this is an aborted request from a client just log it and return if (realError instanceof ClientAbortException ) { if (log.isDebugEnabled()) { log.debug (sm.getString("standardHost.clientAbort", realError.getCause().getMessage())); } return; }
ErrorPage errorPage = findErrorPage(context, throwable); if ((errorPage == null) && (realError != throwable)) { errorPage = findErrorPage(context, realError); }
if (errorPage != null) { response.setAppCommitted(false); request.setAttribute (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR, errorPage.getLocation()); request.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR, new Integer(ApplicationFilterFactory.ERROR)); request.setAttribute (Globals.STATUS_CO new Integer(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); request.setAttribute(Globals.ERROR_MESSAGE_ATTR, throwable.getMessage()); request.setAttribute(Globals.EXCEPTION_ATTR, realError); Wrapper wrapper = request.getWrapper(); if (wrapper != null) request.setAttribute(Globals.SERVLET_NAME_ATTR, wrapper.getName()); request.setAttribute(Globals.EXCEPTION_PAGE_ATTR, request.getRequestURI()); request.setAttribute(Globals.EXCEPTION_TYPE_ATTR, realError.getClass()); if (custom(request, response, errorPage)) { try { response.flushBuffer(); } catch (IOException e) { container.getLogger().warn("Exception Processing " + errorPage, e); } } } else { // A custom error-page has not been defined for the exception // that was thrown during request processing. Check if an // error-page for error co // send that page back as the response. response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // The response is an error response.setError();
status(request, response); }
} |
当发现请求属性中包含异常信息时,就不会将先前渲染过的页面输出给客户端了,而是转而执行以下操作,首先Tomcat检查web.xml中的error配置,然后将500异常映射到指定的JSP页面,如果没有指定error配置,Tomcat则会使用如图1的页面输出响应。这样我们就看到了熟悉的500页面了。
那么这个是Tomcat的问题还是Spring MVC的问题呢?
首先,Tomcat的Servlet/JSP实现是通过Servlet/JSP规范验证的,也就是说,其实现是遵循规范的。而规范也恰恰是这么规定的,所以可以初步的判断是Spring MVC2.5.5的问题了。
如何解决这个问题呢?由Servlet/JSP规范可知,我们可以清理请求中的属性,从而使得Servlet/JSP容器以为处理请求的过程中没有异常发生,响应的状态码为200就可以了。
4 解决方法
方法一:在异常处理JSP页面中添加以下代码:
String ERROR_STATUS_CO
String ERROR_EXCEPTION_TYPE_ATTRIBUTE ="javax.servlet.error.exception_type";
String ERROR_MESSAGE_ATTRIBUTE = "javax.servlet.error.message";
String ERROR_EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception";
String ERROR_REQUEST_URI_ATTRIBUTE ="javax.servlet.error.request_uri";
String ERROR_SERVLET_NAME_ATTRIBUTE ="javax.servlet.error.servlet_name";
request.removeAttribute(ERROR_STATUS_CO
request.removeAttribute(ERROR_EXCEPTION_TYPE_ATTRIBUTE);
request.removeAttribute(ERROR_MESSAGE_ATTRIBUTE);
request.removeAttribute(ERROR_EXCEPTION_ATTRIBUTE);
request.removeAttribute(ERROR_REQUEST_URI_ATTRIBUTE);
request.removeAttribute(ERROR_SERVLET_NAME_ATTRIBUTE);
response.setStatus(HttpServletResponse.SC_OK);
Spring MVC 2.5.6中已经解决了这个问题。在DispatcherServlet类中,检查渲染的是否error视图,如果是则清空请求中的参数。如下图: