Spring MVC在找到你的handler之后,会通过反射调用handler的方法:
public class InvocableHandlerMethod extends HandlerMethod {
/**
* Invoke the handler method with the given argument values.
*/
private Object invoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
throw new IllegalStateException(getInvocationErrorMessage(ex.getMessage(), args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
throw new IllegalStateException(msg, targetException);
}
}
}
}
如果Handler方法抛异常,会被catch住,然后执行相应的异常处理逻辑。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
...
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest, false);
...
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) { // handler抛出的异常会在这里被捕获
dispatchException = ex;
}
// 然后在这里被处理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
handler的异常会在processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
被处理。
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
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);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
....
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
mv = processHandlerException(request, response, handler, exception); 这个方法会处理异常,并且返回一个error ModelAndView。如果mv不为空,还会渲染这个mv。
/**
* Determine an error ModelAndView via the registered HandlerExceptionResolvers.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the time of the exception
* (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to
* @throws Exception if no error ModelAndView found
*/
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()) {
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;
}
这个方法是Spring MVC最重要的异常处理函数。让我们仔细看一下它做的事情。
首先找到注册的HandlerExceptionResolver(异常处理器):
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
然后,调用每个HandlerExceptionResolver的resolveException方法。如果该方法返回的不是null,表示已经处理结束。否则表示交个下一次异常处理器处理。
package org.springframework.web.servlet;
public class DispatcherServlet extends FrameworkServlet {
/** List of HandlerExceptionResolvers used by this servlet */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
private boolean detectAllHandlerExceptionResolvers = true;
/**
* Well-known name for the HandlerExceptionResolver object in the bean factory for this namespace.
* Only used when "detectAllHandlerExceptionResolvers" is turned off.
* @see #setDetectAllHandlerExceptionResolvers
*/
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
/**
* Initialize the HandlerExceptionResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to no exception resolver.
*/
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.
OrderComparator.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");
}
}
}
}
可以看到默认会查找Spring上下文中所有实现了HandlerExceptionResolver接口的类:
- HandlerExceptionResolver
- AbstractHandlerExceptionResolver
- AbstractHandlerMethodExceptionResolver
- ExceptionHandlerExceptionResolver
- AnnotationMethodHandlerExceptionResolver:默认开启,配合@ExceptionHandler注解。@deprecated in favor of ExceptionHandlerExceptionResolver
- DefaultHandlerExceptionResolver:默认开启,处理标准的Spring异常并且将他们转换为相应的HTTP状态码。
- ResponseStatusExceptionResolver:默认开启,配合@ResponseStatus注解将异常映射为HTTP状态码。
- SimpleMappingExceptionResolver
- AbstractHandlerMethodExceptionResolver
- HandlerExceptionResolverComposite
- AbstractHandlerExceptionResolver
经过debug发现,Spring会找到如下异常处理器:
- org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
- org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
- org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
- me.arganzheng.study.springmvc.common.RestHandlerExceptionResolver
特别需要注意的是HandlerExceptionResolver是有顺序的(实现了Ordered接口),如果没有指定,业务自定义的HandlerExceptionResolver会排在最后。
package me.arganzheng.study.springmvc.common;
/**
* 统一异常处理类,将异常以JSON形式返回给用户。
*
* @author zhengzhibin
*/
public class RestHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
private final static Logger log = Logger.getLogger(RestHandlerExceptionResolver.class);
@Autowired
@Qualifier("jsonConverter")
private MappingJsonpHttpMessageConverter jsonConverter;
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
// 异常日志统一在这里记录
log.error(ex.getMessage(), ex);
boolean isApiRequest = false;
if (HttpServletRequestTool.getRequestURIExcludeContextPath(request).startsWith("/api/")) {
isApiRequest = true;
}
MediaType jsonMediaType = getJsonMediaType(request);
if (jsonMediaType == null || isApiRequest) {
jsonMediaType = MediaType.APPLICATION_JSON;
}
try {
writeJsonResponse(ex, response);
} catch (Exception e) {
log.error("Error rendering json response!", e);
}
// ruturn Empty ModelAndView表示到此结束了。
return new ModelAndView();
}
private MediaType getJsonMediaType(HttpServletRequest request) {
List<MediaType> acceptedMediaTypes = new ServletServerHttpRequest(request).getHeaders()
.getAccept();
if (acceptedMediaTypes != null) {
for (MediaType mediaType : acceptedMediaTypes) {
if ("json".equalsIgnoreCase(mediaType.getSubtype())) {
return mediaType;
}
}
}
return null;
}
private void writeJsonResponse(Exception ex, HttpServletResponse response)
throws HttpMessageNotWritableException, IOException {
MediaType jsonMediaType = MediaType.APPLICATION_JSON;
response.setContentType("application/json;charset=utf-8");
if (ex instanceof RestException) {
RestException rex = (RestException) ex;
jsonConverter.write(new RestError(rex.getErrorCode(), ex.getMessage()), jsonMediaType,
new ServletServerHttpResponse(response));
} else if (ex instanceof IllegalArgumentException
|| ex instanceof MissingServletRequestParameterException) {
jsonConverter.write(new RestError(RestErrorCode.BIZ_ARGUMENT_INVALID,
"Invalid Request Parameter: " + ex.getMessage()), jsonMediaType,
new ServletServerHttpResponse(response));
} else if (ex instanceof ResourceNotFoundException) {
jsonConverter.write(new RestError(RestErrorCode.RESOURCE_NOT_FOUND, ex.getMessage()),
jsonMediaType, new ServletServerHttpResponse(response));
} else {
jsonConverter.write(new RestError(RestErrorCode.UNKONW_ERROR, ex.getMessage()),
jsonMediaType, new ServletServerHttpResponse(response));
}
}
}
更新: 使用 @ControllerAdvice (Spring 3.2+) 进行统一异常处理
最近使用 Springboot 2.0.3 RELEASE,对之前的一些 SpringMVC 的使用也一并更新,发现 Spring 3.2 之后引入新的异常处理类——@ControllerAdvice
注解,使用起来更加方便优雅,另外,由于新项目采用前后端分离,页面渲染全部在前端处理了,后端只提供 Restful 接口,处理起来更加统一。具体如下:
package life.arganzheng.internet.ai.ads.predictor.portal.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import life.arganzheng.internet.ai.ads.predictor.common.rest.RestResponse;
import life.arganzheng.internet.ai.ads.predictor.portal.exception.RestException;
import life.arganzheng.internet.ai.ads.predictor.portal.exception.UserNotLoggedInException;
/**
* 统一异常处理类,将异常以JSON形式返回给用户。在这里主要是给前端JS用。
*
* @author zhengzhibin
* @date 2018/09/28
*/
@ControllerAdvice
public class RestResponseExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RestResponseExceptionHandler.class);
@ResponseBody
@ExceptionHandler(value = {RuntimeException.class})
public RestResponse<?> handleException(RuntimeException ex) {
// 异常日志统一在这里记录
if (!(ex instanceof UserNotLoggedInException)) {
LOGGER.error(ex.getMessage(), ex);
}
return new RestResponse<>(ErrorCode.UNKNOWN_ERROR.getErrorCode(), ErrorCode.UNKNOWN_ERROR.getErrorMessage());
}
@ResponseBody
@ExceptionHandler(value = {IllegalArgumentException.class, MissingServletRequestParameterException.class,
JsonProcessingException.class})
public RestResponse<?> handleInvalidArgumentException(RuntimeException ex) {
LOGGER.error(ex.getMessage(), ex);
return new RestResponse<>(ErrorCode.INVALID_ARGUMENT.getErrorCode(),
"Invalid Request Parameter: " + ex.getMessage());
}
@ResponseBody
@ExceptionHandler(value = {RestException.class})
public RestResponse<?> handleRestException(RestException ex) {
LOGGER.error(ex.getMessage(), ex);
return new RestResponse<>(ex.getErrorCode(), ex.getMessage());
}
}
其中 RestResponse
定义如下:
package life.arganzheng.internet.ai.ads.predictor.portal.common;
/**
* <pre>
* REST 返回格式,这样可以利用@ResponseBody注解。
* 这里使用errorCode和errorMessage,而不是Http BookStatus Code。不是纯粹的RESTful规范,不过个人觉得简单一下。
*
* </pre>
*
* @author zhengzhibin
* @date 2018/09/05
*/
public class RestResponse<T> {
public static final RestResponse SUCCESS = new RestResponse();
public static final RestResponse ERROR_UNKNOWN =
new RestResponse(ErrorCode.UNKONW_ERROR.getErrorCode(), "Unknow Error!");
private static final String EMPTY_STRING = "";
private int errorCode = 0;
private String errorMessage = EMPTY_STRING;
private T data;
public RestResponse(int errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public RestResponse() {}
public RestResponse(T data) {
this.data = data;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
RestException
定义如下:
package life.arganzheng.internet.ai.ads.predictor.portal.exception;
/**
* @author zhengzhibin
* @date 2018/09/28
*/
public class RestException extends PredictorPortalException {
private static final long serialVersionUID = -900803978044950928L;
private int errorCode;
public RestException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public RestException(int errorCode, String message, Throwable ex) {
super(message, ex);
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
}
ErrorCode
是一个枚举类型:
package life.arganzheng.internet.ai.ads.predictor.portal.common;
/**
* 错误代码常量定义
*
* @author zhengzhibin
* @date 2018/09/03
*/
public enum ErrorCode {
/**
* 成功
*/
SUCCESS(0),
/**
* 未知错误
*/
UNKNOWN_ERROR(520, "Unknown error."),
/**
* 非法输入
*/
INVALID_ARGUMENT(401, "Invalid argument."),
/**
* 用户没有登录
*/
USER_NOT_LOGIN(403, "User not login."),
/**
* 资源未找到
*/
RESOURCE_NOT_FOUND(404, "Resource not found."),
/**
* 资源已经存在
*/
RESOURCE_ALREADY_EXIST(405, "Resource already exist."),
private int errorCode;
private String errorMessage;
ErrorCode(int errorCode) {
this.errorCode = errorCode;
}
ErrorCode(int errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public int getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
}
参考文章: Error Handling for REST with Spring
推荐阅读
- Error Handling for REST with Spring
- Exception Handling in Spring MVC
- Error Handling for REST with Spring