在Spring MVC中,所有用于处理在请求处理过程中抛出的异常,都要实现HandlerExceptionResolver接口。HandlerExceptionResolver是Spring MVC提供的非常好的通用异常处理工具,不过需要注意的是,它只能处理请求过程中抛出的异常,异常处理本身所抛出的异常和视图解析过程中抛出的异常它是不能处理的。AbstractHandlerExceptionResolver实现该接口和Orderd接口,是HandlerExceptionResolver类的实现的基类。ResponseStatusExceptionResolver等具体的异常处理类均在AbstractHandlerExceptionResolver之上,实现了具体的异常处理方式。一个基于Spring MVC的Web应用程序中,可以存在多个实现了HandlerExceptionResolver的异常处理类,他们的执行顺序,由其order属性决定, order值越小,越是优先执行, 在执行到第一个返回不是null的ModelAndView的Resolver时,不再执行后续的尚未执行的Resolver的异常处理方法。
mvc:annotation-driven/会自动将ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver配置到Spring MVC中,并且其中ExceptionHandlerExceptionResolver优先级最高,ResponseStatusExceptionResolver第二,DefaultHandlerExceptionResolver第三。如果你想使用SimpleMappingExceptionResolver,你需要自己将SimpleMappingExceptionResolver配置到Spring MVC中**。另外ExceptionHandlerExceptionResolver不仅可以解析处理器类中注解的@ExceptionHandler的方法,还可以使用@ControllerAdvice注解的类里的有@ExceptionHandler注解的全局异常处理方法**。
我们首先看一下HandlerExceptionResolver家族体系的结构:
其中HandlerExceptionResolverComposite作为容器使用,可以封装别的Resolver,它并不会解析具体的异常,而是调用其他的异常解析器处理异常。这里我们不需要去研究它
而AnnotationMethodHandlerExceptionResolver已经被弃用了,所以不需要解析。剩下的是我们研究的重点:
-
AbstractHandlerMethodExceptionResolver和ExceptionHandlerExceptionResolver一起使用,完成使用@ExceptionHandler注释的方法进行对异常的解析。
-
ResponseStatusExceptionResolver: 解析有@ResponseStatus注解的异常。
-
DefaultHandlerExceptionResolver:按照不同的类型分别对异常进行解析。
-
SimpleMappingExceptionResolver: 通过配置的异常类和view的对应关系来解析异常。
下面我们具体的分析这些异常解析器。
首先我们HandlerExceptionResolver接口的源码,我去掉了源码中不关心的注释的部分:
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
Spring MVC的异常处理只是处理请求处理过程中异常,既然是"请求处理过程中"的异常,我们必然会知道本次请求的handler对象,以及被抛出的Exception对象,既然是本次请求,肯定还会出现与请求有关的request和response对象。这就很好的说明了为什么resolveException方法中会出现这四个参数。
为什么我一直都说Spring MVC的异常处理体系只是处理请求处理过程的异常呢?我们来分析下源码,下面的源码出自DispatcherServlet的doDispatch方法,我省略了部分不重要的内容:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
//请求处理的代码
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
finally {
}
}
在请求处理过程中发生的异常,都会进入到processDispatchResult方法中,我去掉了不关心的部分:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
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);
}
}
}
会发现这个方法遍历了handlerExceptionResolvers异常解析器列表,这个handlerExceptionResolvers异常解析器列表是DispatcherServlet初始化的时候创建的。通常handlerExceptionResolvers异常解析器列表里面包含了上面我们所讲述的ExceptionHandlerExceptionResolver和ResponseStatusExceptionResolver以及DefaultHandlerExceptionResolver。
HandlerExceptionResolver异常体系中使用到模板设计模式,HandlerExceptionResolver接口定义了处理异常的标准API,而AbstractHandlerExceptionResolver则定义了处理异常的步骤。下面是AbstractHandlerExceptionResolver源码
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
prepareResponse(ex, response);
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
第一步:判断当前所用的异常处理器是否可以处理当前的handler。如果不可以,就返回null, 这样在DispatcherServlet里面就会继续遍历handlerExceptionResolvers异常解析器列表,寻找下一个异常解析器来处理当前的handler。如果可以,就继续进行下面的第二步。那么它是如何判断的呢?我们来研究下shouldApplyTo方法:
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class<?> handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
this.mappedHandlers代表当前的异常处理器可以处理哪些handler,如果目标handler非空,并且异常处理器可以处理的handler包含目标handler,我们就说这个异常处理器可以处理目标handler的异常。下面的if条件的内容与此相似。
第二步:logException就是将exception打印出来。
第三步:prepareResponse方法根据preventResponseCaching标志判断是否给response设置禁用缓存的属性。
第四步:doResolveException方法是模板方法,至于具体的如何处理异常,应该交给具体的异常解析器来进行处理。
ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver的父类AbstractHandlerMethodExceptionResolver重写了shouldApplyTo方法:
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler == null) {
return super.shouldApplyTo(request, handler);
}
else if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
}
else {
return false;
}
}
在这里我们可以看出,如果我们的handler为空,就调用父类的shouldApplyTo方法,如果handler非空,就判断handler是否是属于HandlerMethod类型,如果是就获取HandlerMethod处理器的类型信息,赋给当前的handler,然后还是调用父类的shouldApplyTo方法进行处理,否则返回false。
AbstractHandlerMethodExceptionResolver的作用就相当于一个适配器,一般的处理器是类的形式,但是HandlerMethod其实是讲方法作为处理器来使用的,所以需要适配。
AbstractHandlerMethodExceptionResolver里面的doResolveException将处理传递给了doResolveHandlerMethodException方法具体处理,而doResolveHandlerMethodException是一个模板方法,由ExceptionHandlerExceptionResolver具体实现。
ResponseStatusExceptionResolver
现在我们看下ResponseStatusExceptionResolver的doResolveException方法:
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
try {
return resolveResponseStatus(responseStatus, request, response, handler, ex);
}
catch (Exception resolveEx) {
logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
}
}
return null;
}
首先获取当前的exception是否存在@ResponseStatus注解,如果存在,就使用resolveResponseStatus方法处理.
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
int statusCode = responseStatus.value().value();
String reason = responseStatus.reason();
if (this.messageSource != null) {
reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale());
}
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
}
else {
response.sendError(statusCode, reason);
}
return new ModelAndView();
}
获取@ResponseStatus注解中的value和reason的值,然后设置到当前的response中去。
DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver是根据异常的具体类型来进行处理:
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response,
handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
各种异常解析器的使用
在我们自定义的异常上使用ResponseStatus注解。当我们的Controller抛出异常,并且没有被处理的时候,他将返回HTTP STATUS 为指定值的 HTTP RESPONSE,比如:
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}
这时候会返回404,转到404页面而不是错误页面。
在一个Controller中,通过增加使用注解@ExceptionHandler的方法来处理@RequestMapping方法抛出的异常,注意这种只在单个Controller中有效。这么做可以:
发生异常后,改变Response status,一般而言,发生异常返回HTTP STATUS 500.我们可以变为其他。
发生错误后转到错误页面
可以为不同异常定义不同处理(如不同的错误页面,不同的Response status)
@Controller
public class ExceptionHandlingController {
// 我们标注了@RequestMapping的方法
...
//处理异常的方法。
// 把我们定义的异常转换为特定的Http status code
@ResponseStatus(value=HttpStatus.CONFLICT, reason="Data integrity violation") // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void conflict() {
// Nothing to do
}
// 捕获到SQLException,DataAccessException异常之后,转到特定的页面。
@ExceptionHandler({SQLException.class,DataAccessException.class})
public String databaseError() {
//仅仅转到错误页面,我们在页面上得不到这个Exception的值,要得到值,我们可以通过下面的方法得到
return "databaseError";
}
// 通过ModelAndView返回页面,以及往页面传相应的值
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception exception) {
logger.error("Request: " + req.getRequestURL() + " raised " + exception);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", exception);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
在类上使用 @ControllerAdvice注解,可以使得我们处理整个程序中抛出的异常。然后在类中的方法上使用@ExceptionHandler来定义处理不同的异常。举例:
class GlobalControllerExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
//转到特定页面 。。。。。
}
如果我们要处理程序中所有的异常可以这么做:
@ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with @ResponseStatus rethrow it and let
// the framework handle it - like the OrderNotFoundException example
// at the start of this post.
// AnnotationUtils is a Spring Framework utility class.
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}