Spring MVC错误页面配置

当前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;
}

由此可见:

SimpleMappingExceptionResolver通过 exceptionMappings和statusCodes来确立Exception、http状态码以及view之间的映射关系。明白这个就很简单了,我们可以通过设置exceptionMappings、statusCodes的值来实现我们自定义的映射关系。

实战

页面准备

  1. 我们在WEB-INF/views/commons/error(目录自己定)新建我们自定义的错误页面,404.html, 500.html等等。

  2. SimpleMappingExceptionResolver只实现映射关系,我们还需要通过配置web.xml来实现。

     <error-page>
         <error-code>404</error-code>
         <location>/error/404.html</location>
     </error-page>
     
     <error-page>
         <error-code>500</error-code>
         <location>/error/500.html</location>
     </error-page>
    
  3. 在spring-mvc配置文件中将404.html、500.html等设置为资源文件,避免被springmvc再次拦截。

     <mvc:resources mapping="/error/**" location="/WEB-INF/views/commons/error/" />
    
  4. 配置SimpleMappingExceptionResolver。

     <bean class="org.springframework.web.servlet.handler. SimpleMappingExceptionResolver">
         <property name="exceptionMappings">
             <map>
                 <entry key="ResourceNotFoundException" value="common/error/resourceNotFoundError" />
                 <entry key=".DataAccessException" value="common/error/dataAccessError" />
             </map>
         </property>
         <property name="statusCodes">
             <map>
                 <entry key="common/error/resourceNotFoundError" value="404" />
                 <entry key="common/error/dataAccessError" value="500" />
             </map>
         </property>
     </bean>	
    

到此,就实现我们需要的配置了。

欢迎访问我的独立博客:

www.javafan.cn

原文地址:https://www.cnblogs.com/dongying/p/6129937.html