下圖中,我畫出了Spring MVC中,跟異常處理相關的主要類和接口。java
在Spring MVC中,全部用於處理在請求映射和請求處理過程當中拋出的異常的類,都要實現HandlerExceptionResolver接口。AbstractHandlerExceptionResolver實現該接口和Orderd接口,是HandlerExceptionResolver類的實現的基類。ResponseStatusExceptionResolver等具體的異常處理類均在AbstractHandlerExceptionResolver之上,實現了具體的異常處理方式。一個基於Spring MVC的Web應用程序中,能夠存在多個實現了HandlerExceptionResolver的異常處理類,他們的執行順序,由其order屬性決定, order值越小,越是優先執行, 在執行到第一個返回不是null的ModelAndView的Resolver時,再也不執行後續的還沒有執行的Resolver的異常處理方法。。web
下面我逐個介紹一下SpringMVC提供的這些異常處理類的功能。spring
HandlerExceptionResolver接口的默認實現,基本上是Spring MVC內部使用,用來處理Spring定義的各類標準異常,將其轉化爲相對應的HTTP Status Code。其處理的異常類型有:spring-mvc
handleNoSuchRequestHandlingMethod handleHttpRequestMethodNotSupported handleHttpMediaTypeNotSupported handleMissingServletRequestParameter handleServletRequestBindingException handleTypeMismatch handleHttpMessageNotReadable handleHttpMessageNotWritable handleMethodArgumentNotValidException handleMissingServletRequestParameter handleMissingServletRequestPartException handleBindException
用來支持ResponseStatus的使用,處理使用了ResponseStatus註解的異常,根據註解的內容,返回相應的HTTP Status Code和內容給客戶端。若是Web應用程序中配置了ResponseStatusExceptionResolver,那麼咱們就可使用ResponseStatus註解來註解咱們本身編寫的異常類,並在Controller中拋出該異常類,以後ResponseStatusExceptionResolver就會自動幫咱們處理剩下的工做。mvc
這是一個本身編寫的異常,用來表示訂單不存在:app
@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"; }
這樣,當OrderNotFoundException被拋出時,ResponseStatusExceptionResolver會返回給客戶端一個HTTP Status Code爲404的響應。async
用來支持ExceptionHandler註解,使用被ExceptionHandler註解所標記的方法來處理異常。其中AnnotationMethodHandlerExceptionResolver在3.0版本中開始提供,ExceptionHandlerExceptionResolver在3.1版本中開始提供,從3.2版本開始,Spring推薦使用ExceptionHandlerExceptionResolver。
若是配置了AnnotationMethodHandlerExceptionResolver和ExceptionHandlerExceptionResolver這兩個異常處理bean之一,那麼咱們就可使用ExceptionHandler註解來處理異常。ide
下面是幾個ExceptionHandler註解的使用例子:post
@Controller public class ExceptionHandlingController { // @RequestHandler methods ... // 如下是異常處理方法 // 將DataIntegrityViolationException轉化爲Http Status Code爲409的響應 @ResponseStatus(value=HttpStatus.CONFLICT, reason="Data integrity violation") // 409 @ExceptionHandler(DataIntegrityViolationException.class) public void conflict() { // Nothing to do } // 針對SQLException和DataAccessException返回視圖databaseError @ExceptionHandler({SQLException.class,DataAccessException.class}) public String databaseError() { // Nothing to do. Returns the logical view name of an error page, passed to // the view-resolver(s) in usual way. // Note that the exception is _not_ available to this view (it is not added to // the model) but see "Extending ExceptionHandlerExceptionResolver" below. return "databaseError"; } // 建立ModleAndView,將異常和請求的信息放入到Model中,指定視圖名字,並返回該ModleAndView @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; } }
須要注意的是,上面例子中的ExceptionHandler方法的做用域,只是在本Controller類中。若是須要使用ExceptionHandler來處理全局的Exception,則須要使用ControllerAdvice註解。
@ControllerAdvice class GlobalDefaultExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { // 若是異常使用了ResponseStatus註解,那麼從新拋出該異常,Spring框架會處理該異常。 if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) throw e; // 不然建立ModleAndView,處理該異常。 ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } }
提供了將異常映射爲視圖的能力,高度可定製化。其提供的能力有:
@Configuration @EnableWebMvc public class MvcConfiguration extends WebMvcConfigurerAdapter { @Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError"); mappings.setProperty("InvalidCreditCardException", "creditCardError"); r.setExceptionMappings(mappings); // 默認爲空 r.setDefaultErrorView("error"); // 默認沒有 r.setExceptionAttribute("ex"); r.setWarnLogCategory("example.MvcLogger"); return r; } ... }
Spring MVC的異常處理很是的靈活,若是提供的ExceptionResolver類不能知足使用,咱們能夠實現本身的異常處理類。能夠經過繼承SimpleMappingExceptionResolver來定製Mapping的方式和能力,也能夠直接繼承AbstractHandlerExceptionResolver來實現其它類型的異常處理類。
首先看Spring MVC是怎麼加載異常處理bean的。
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
如下代碼摘自ispatcherServlet,描述了異常處理類的加載過程:
/** * Initialize the HandlerMappings used by this class. * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace, * we default to BeanNameUrlHandlerMapping. */ private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); // We keep HandlerMappings in sorted order. OrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
而後看Spring MVC是怎麼使用異常處理bean的。
如下代碼摘自DispatcherServlet,描述了處理異常的過程。
/** * Process the actual dispatching to the handler. * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters * to find the first that supports the handler class. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers * themselves to decide which methods are acceptable. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure */ 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 { 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()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { 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 if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } /** * 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()) { 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; }
Spring提供了不少選擇和很是靈活的使用方式,下面是一些使用建議: