异常处理说明
在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注解的全局异常处理方法。
核心接口
HandlerExceptionExceptionResolver
用于解析请求处理过程中出现的异常;
主要功能:给ModelAndView设置内容、设置response相关的属性;
对应的子类
AbstractHandlerMethodExceptionResolver
ResponseStatusExceptionResolver 优先级第二高
AnnotationMethodHandlerExceptionResolver : 已废弃
DefaultHandlerExceptionResolver
SimpleMappingExceptionResolver
ExceptionHandlerExceptionResolver:优先级最高
-
AbstractHandlerMethodExceptionResolver和ExceptionHandlerExceptionResolver一起使用,完成使用@ExceptionHandler注释的方法进行对异常的解析。
-
ResponseStatusExceptionResolver: 解析有@ResponseStatus注解的异常。
-
DefaultHandlerExceptionResolver:按照不同的类型分别对异常进行解析。
-
SimpleMappingExceptionResolver: 通过配置的异常类和view的对应关系来解析异常。
为什么我一直都说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);
}
}
}
会发现在这个方法实际调用的是processHandlerException方法,我去掉了不关心的部分:
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
throw ex;
}
会发现这个方法遍历了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;
}
}
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;
}
}
各种异常解析器的使用
在我们自定义的异常上使用ResponseStatus注解。当我们的Controller抛出异常,并且没有被处理的时候,他将返回HTTP STATUS 为指定值的 HTTP RESPONSE,比如:
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}
我们的Controller为:
@RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(@PathVariable("id") long id, Model model) {
Order order = orderRepository.findOrderById(id);
if (order == null) throw new OrderNotFoundException(id);
model.addAttribute(order);
return "orderDetail";
}
这时候会返回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;
}
}
参考:https://blog.csdn.net/pfnie/article/details/52416943