SpringMVC源码(五)-异常处理


声明:本文转载自https://my.oschina.net/u/2377110/blog/1552104,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

SpringMVC除了对请求URL的路由处理特别方便外,还支持对异常的统一处理机制,可以对业务操作时抛出的异常,unchecked异常以及状态码的异常进行统一处理。SpringMVC既提供简单的配置类,也提供了细粒度的异常控制机制。

SpringMVC中所有的异常处理通过接口HandlerExceptionResolver来实现,接口中只定义了一个方法

public interface HandlerExceptionResolver {  	ModelAndView resolveException( 		HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); } 

方法中接受request和response信息,以及当前的处理Handler,和抛出的异常对象。并且提供抽象类AbstractHandlerExceptionResolver,实现resolveException方法,支持前置判断和处理,将实际处理抽象出doResolveException方法由子类来实现。

1.SimpleMappingExceptionResolver

SimpleMappingExceptionResolver是SpringMVC提供的一个非常便捷的简易异常处理方式,在XML中进行配置即可使用。

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> 	<!-- 默认异常视图 --> 	<property name="defaultErrorView" value="error"/> 	<!-- 视图中获取exception信息变量名 --> 	<property name="exceptionAttribute" value="ex"></property> 	<!-- 异常同视图映射关系 --> 	<property name="exceptionMappings"> 		<props> 			<prop key="com.lcifn.springmvc.exception.BusinessException">businessEx</prop> 		</props> 	</property> </bean> 

这是极简的一种配置,exceptionMappings配置的是异常同视图之间的映射关系,它是一个Properties对象,key-value分别是异常的类路径和视图名称。defaultErrorView表示默认异常视图,如果抛出的异常没有匹配到任何视图,即会走默认异常视图。exceptionAttribute表示在视图中获取exception信息变量名,默认为exception。还有一些其他配置可以查看SimpleMappingExceptionResolver的源码来使用。

2.@ExceptionHandler

SpringMVC提供了一种注解方式来灵活地配置异常处理,@ExceptionHandler中可以配置要处理的异常类型,然后定义在处理此种异常的方法上,方法只要写在Controller中,即可对Controller中所有请求方法有效。

我们定义一个BaseController,并且将需要处理的异常通过@ExceptionHandler定义好处理方法,这样业务Controller只需要继承这个基类就可以了。处理方法中支持Request/Response/Sessioin等相关的参数绑定。

[@Controller](https://my.oschina.net/u/1774615) public class BaseController {  	@ExceptionHandler(RuntimeException.class) 	public ModelAndView handleRuntimeException(HttpServletRequest req, HttpServletResponse resp, RuntimeException ex){ 		return new ModelAndView("error"); 	} } 

但是继承的方式还是对业务代码造成侵入,Spring非常重要的特性就是非侵入性,因而SpringMVC提供了@ControllerAdvice,简单来说就是Controller的切面,支持对可选择的Controller进行统一配置,用于异常处理简直再合适不过了,我们只需要将BaseController稍稍改一下。

@ControllerAdvice public class AdviceController {  	@ExceptionHandler(RuntimeException.class) 	public ModelAndView handleRuntimeException(HttpServletRequest req, HttpServletResponse resp, RuntimeException ex){ 		return new ModelAndView("error"); 	} } 

只需要在统一配置类上加上@ControllerAdvice注解,支持包路径,注解等过滤方式,即可完成对所有业务Controller进行控制,而业务Controller不用做anything。

如果请求为ajax方式,需要其他格式返回异常,在方法上加上@ResponseBody即可。

3.异常处理原理

上面介绍了常用的两种异常处理的配置方式,所谓知其然要知其所以然,SpringMVC怎么在请求处理的过程中完成对异常的统一处理的呢?我们从源码来深度解读。

回到DispatcherServlet的doDispatcher方法

try { 	processedRequest = checkMultipart(request); 	multipartRequestParsed = (processedRequest != request);  	// Determine handler for the current request. 	mappedHandler = getHandler(processedRequest); 	if (mappedHandler == null || mappedHandler.getHandler() == null) { 		noHandlerFound(processedRequest, response); 		return; 	}  	// Determine handler adapter for the current request. 	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  	if (!mappedHandler.applyPreHandle(processedRequest, response)) { 		return; 	}  	// Actually invoke the handler. 	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  	if (asyncManager.isConcurrentHandlingStarted()) { 		return; 	}  	applyDefaultViewName(processedRequest, mv); 	mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { 	dispatchException = ex; } catch (Throwable err) { 	dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 

可以看到对请求处理的核心处理使用一个大的try/catch,如果出现异常,统一封装成dispatchException交给processDispatchResult方法进行处理。我们知道processDispatchResult方法用来对返回视图进行操作,而同时也对异常进行统一处理。

在processDispatchResult中,首先对异常进行判断。

if (exception != null) { 	if (exception instanceof ModelAndViewDefiningException) { 		logger.debug("ModelAndViewDefiningException encountered", exception); 		mv = ((ModelAndViewDefiningException) exception).getModelAndView(); 	} 	else { 		Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 		mv = processHandlerException(request, response, handler, exception); 		errorView = (mv != null); 	} } 

如果不是特殊的ModelAndViewDefiningException,则由processHandlerException来操作。

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调用实现类的实际实现方法doResolveException。

SimpleMappingExceptionResolver

来看SimpleMappingExceptionResolver的实现:

protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, 		Object handler, Exception ex) {  	// Expose ModelAndView for chosen error view. 	// 根据request和异常对象获取异常视图名称 	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); 		} 		// 组装异常视图模型ModelAndView 		return getModelAndView(viewName, ex, request); 	} 	else { 		return null; 	} } 

determineViewName方法决定异常视图名称,getModelAndView方法返回ModelAndView对象

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

在determineViewName方法中,我们配置的defaultErrorView和exceptionMappings都起了作用。更细节的就不深入了,有兴趣可以自己去看。

ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver支持了@ExceptionHandler注解的实现。它的抽象基类AbstractHandlerMethodExceptionResolver继承了AbstractHandlerExceptionResolver,doResolveException方法实际调用ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法。

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, 		HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {  	// 根据HandlerMethod和exception获取异常处理的Method 	ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); 	if (exceptionHandlerMethod == null) { 		return null; 	}  	// 设置异常处理方法的参数解析器和返回值解析器 	exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); 	exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);  	ServletWebRequest webRequest = new ServletWebRequest(request, response); 	ModelAndViewContainer mavContainer = new ModelAndViewContainer();  	// 执行异常处理方法 	try { 		if (logger.isDebugEnabled()) { 			logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod); 		} 		Throwable cause = exception.getCause(); 		if (cause != null) { 			// Expose cause as provided argument as well 			exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); 		} 		else { 			// Otherwise, just the given exception as-is 			exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); 		} 	} 	catch (Throwable invocationEx) { 		// Any other than the original exception is unintended here, 		// probably an accident (e.g. failed assertion or the like). 		if (invocationEx != exception && logger.isWarnEnabled()) { 			logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx); 		} 		// Continue with default processing of the original exception... 		return null; 	}  	// 对返回的视图模型进行处理 	if (mavContainer.isRequestHandled()) { 		return new ModelAndView(); 	} 	else { 		ModelMap model = mavContainer.getModel(); 		HttpStatus status = mavContainer.getStatus(); 		ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); 		mav.setViewName(mavContainer.getViewName()); 		if (!mavContainer.isViewReference()) { 			mav.setView((View) mavContainer.getView()); 		} 		if (model instanceof RedirectAttributes) { 			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); 			request = webRequest.getNativeRequest(HttpServletRequest.class); 			RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); 		} 		return mav; 	} } 

我们主要关注的是如何匹配到异常处理方法的

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { 	Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);  	// 从当前Controller中匹配异常处理Method 	if (handlerMethod != null) { 		ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); 		if (resolver == null) { 			resolver = new ExceptionHandlerMethodResolver(handlerType); 			this.exceptionHandlerCache.put(handlerType, resolver); 		} 		Method method = resolver.resolveMethod(exception); 		if (method != null) { 			return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); 		} 	}  	// 从ControllerAdvice中匹配异常处理Method 	for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { 		if (entry.getKey().isApplicableToBeanType(handlerType)) { 			ExceptionHandlerMethodResolver resolver = entry.getValue(); 			Method method = resolver.resolveMethod(exception); 			if (method != null) { 				return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method); 			} 		} 	}  	return null; } 

匹配异常处理方法的来源有两个,一个是当前Controller,一个是所有@ControllerAdvice类。可以看到两种方式都使用了cache的方式,那么ExceptionHandlerMethod的信息怎么初始化的呢?

当前Controller

对每个请求HandlerMethod的Controller类型,都实例化一个ExceptionHandlerMethodResolver来处理异常。ExceptionHandlerMethodResolver的构造函数中初始化了当前Controller中的异常处理配置。

public ExceptionHandlerMethodResolver(Class<?> handlerType) { 	for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { 		// detectExceptionMappings方法执行探查 		for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { 			addExceptionMapping(exceptionType, method); 		} 	} }  private List<Class<? extends Throwable>> detectExceptionMappings(Method method) { 	List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>(); 	// 探查所有ExceptionHandler注解的方法 	detectAnnotationExceptionMappings(method, result); 	if (result.isEmpty()) { 		for (Class<?> paramType : method.getParameterTypes()) { 			if (Throwable.class.isAssignableFrom(paramType)) { 				result.add((Class<? extends Throwable>) paramType); 			} 		} 	} 	if (result.isEmpty()) { 		throw new IllegalStateException("No exception types mapped to " + method); 	} 	return result; }  protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) { 	ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class); 	result.addAll(Arrays.asList(ann.value())); } 

@ControllerAdvice类

对@ControllerAdvice统一切面类的处理,则是在ExceptionHandlerExceptionResolver的初始化方法afterPropertiesSet中进行处理。

public void afterPropertiesSet() { 	// Do this first, it may add ResponseBodyAdvice beans 	// 初始化@ControllerAdvice中的@ExceptionHandler 	initExceptionHandlerAdviceCache();  	if (this.argumentResolvers == null) { 		List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); 		this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); 	} 	if (this.returnValueHandlers == null) { 		List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); 		this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); 	} } 

initExceptionHandlerAdviceCache方法遍历上下文中所有有@ControllerAdvice注解的Bean,然后实例化成ExceptionHandlerMethodResolver类,在构造函数中初始化所有@ExceptionHandler。

private void initExceptionHandlerAdviceCache() { 	if (getApplicationContext() == null) { 		return; 	} 	if (logger.isDebugEnabled()) { 		logger.debug("Looking for exception mappings: " + getApplicationContext()); 	}  	// 查询所有@ControllerAdvice的Bean 	List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); 	AnnotationAwareOrderComparator.sort(adviceBeans);  	// 遍历,实例化ExceptionHandlerMethodResolver 	for (ControllerAdviceBean adviceBean : adviceBeans) { 		ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType()); 		if (resolver.hasExceptionMappings()) { 			this.exceptionHandlerAdviceCache.put(adviceBean, resolver); 			if (logger.isInfoEnabled()) { 				logger.info("Detected @ExceptionHandler methods in " + adviceBean); 			} 		} 		 		if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) { 			this.responseBodyAdvice.add(adviceBean); 			if (logger.isInfoEnabled()) { 				logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean); 			} 		} 	} } 

匹配到exceptionHandlerMethod后,设置一些方法执行的环境,然后调用ServletInvocableHandlerMethod中的invokeAndHandle去执行,这个调用过程和正常请求的调用就是一致了。这里也不向下扩展了,可以参看SpringMVC源码(四)-请求处理

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, 		Object... providedArgs) throws Exception {  	// 执行请求方法 	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); 	setResponseStatus(webRequest);  	if (returnValue == null) { 		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { 			mavContainer.setRequestHandled(true); 			return; 		} 	} 	else if (StringUtils.hasText(getResponseStatusReason())) { 		mavContainer.setRequestHandled(true); 		return; 	}  	mavContainer.setRequestHandled(false); 	try { 		this.returnValueHandlers.handleReturnValue( 				returnValue, getReturnValueType(returnValue), mavContainer, webRequest); 	} 	catch (Exception ex) { 		if (logger.isTraceEnabled()) { 			logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); 		} 		throw ex; 	} } 

至此ExceptionHandlerExceptionResolver的异常处理已经基本完成。SpringMVC还内置了ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver来对状态码异常和常见的请求响应异常进行统一处理。

4.web.xml的配置

某些情况下,SpringMVC的处理并没有异常出现,但在最终的视图输出时找不到视图文件,就会显示404错误页面,非常影响用户体验。我们可以在web.xml中对未捕获的异常以及此种4xx或5xx的异常通过进行处理。

<error-page> 	<error-code>404</error-code> 	<location>/404</location> </error-page> <error-page> 	<exception-type>java.lang.Throwable</exception-type> 	<location>/500</location> </error-page> 

通常对于系统中的异常,业务相关的尽量自定义异常处理方式,而一些系统异常通过统一错误页面进行处理。

本文发表于2017年10月18日 00:34
(c)注:本文转载自https://my.oschina.net/u/2377110/blog/1552104,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1866 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1