当前SpringMVC非常流行,在大多数情况,我们都需要自定义一些错误页面(例如:401, 402, 403, 500...),以便更友好的提示。对于spring mvc,这些当然是支持自定义的,spring是怎么做的? 还是去看看spring的源码吧:
原理
DispatcherServlet
众所周知,springmvc的入口是DispatcherServlet, 在DispatcherServlet的源码中,不知你是否注意到了以下方法:
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex)
throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv =
null;
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;
}
这个方法就是springmvc对于异常的处理,其调用了HandlerExceptionResolver的resolveException方法。HandlerExceptionResolver有众多实现类,其中,重点看看
SimpleMappingExceptionResolver(我们就是要通过它来配置自定义错误页面)。
SimpleMappingExceptionResolver
public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
...
private Properties exceptionMappings;
private Class<?>[] excludedExceptions;
private Map<
String, Integer> statusCodes =
new HashMap<
String, Integer>();
...
public void setExceptionMappings(Properties mappings) {
this.exceptionMappings = mappings;
}
public void setStatusCodes(Properties statusCodes) {
for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) {
String viewName = (
String) enumeration.nextElement();
Integer statusCode =
new Integer(statusCodes.getProperty(viewName));
this.statusCodes.put(viewName, statusCode);
}
}
}
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
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;
}
}
protected String determineViewName(Exception ex, HttpServletRequest request) {
String viewName =
null;
if (
this.excludedExceptions !=
null) {
for (Class<?> excludedEx :
this.excludedExceptions) {
if (excludedEx.equals(ex.getClass())) {
return null;
}
}
}
// Check for specific exception mappings.
if (
this.exceptionMappings !=
null) {
viewName = findMatchingViewName(
this.exceptionMappings, ex);
}
// Return default error view else, if defined.
if (viewName ==
null &&
this.defaultErrorView !=
null) {
if (logger.isDebugEnabled()) {
logger.debug(
"Resolving to default view '" +
this.defaultErrorView +
"' for exception of type [" +
ex.getClass().getName() +
"]");
}
viewName =
this.defaultErrorView;
}
return viewName;
}
protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
String viewName =
null;
String dominantMapping =
null;
int deepest = Integer.MAX_VALUE;
for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
String exceptionMapping = (
String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if (depth >=
0 && (depth < deepest || (depth == deepest &&
dominantMapping !=
null && exceptionMapping.length() > dominantMapping.length()))) {
deepest = depth;
dominantMapping = exceptionMapping;
viewName = exceptionMappings.getProperty(exceptionMapping);
}
}
if (viewName !=
null && logger.isDebugEnabled()) {
logger.debug(
"Resolving to view '" + viewName +
"' for exception of type [" + ex.getClass().getName() +
"], based on exception mapping [" + dominantMapping +
"]");
}
return viewName;
}
protected Integer determineStatusCode(HttpServletRequest request,
String viewName) {
if (
this.statusCodes.containsKey(viewName)) {
return this.statusCodes.get(viewName);
}
return this.defaultStatusCode;
}
由此可见: