Spring MVC異常處理詳解
Spring MVC中異常處理的類體系結構
下圖中,我畫出了Spring MVC中,跟異常處理相關的主要類和介面。
在Spring MVC中,所有用於處理在請求映射和請求處理過程中拋出的異常的類,都要實現HandlerExceptionResolver介面。AbstractHandlerExceptionResolver實現該介面和Orderd介面,是HandlerExceptionResolver類的實現的基類。ResponseStatusExceptionResolver等具體的異常處理類均在AbstractHandlerExceptionResolver之上,實現了具體的異常處理方式。一個基於Spring MVC的Web應用程序中,可以存在多個實現了HandlerExceptionResolver的異常處理類,他們的執行順序,由其order屬性決定, order值越小,越是優先執行, 在執行到第一個返回不是null的ModelAndView的Resolver時,不再執行後續的尚未執行的Resolver的異常處理方法。。
下面我逐個介紹一下SpringMVC提供的這些異常處理類的功能。
DefaultHandlerExceptionResolver
HandlerExceptionResolver介面的默認實現,基本上是Spring MVC內部使用,用來處理Spring定義的各種標準異常,將其轉化為相對應的HTTP Status Code。其處理的異常類型有:
handleNoSuchRequestHandlingMethodhandleHttpRequestMethodNotSupportedhandleHttpMediaTypeNotSupportedhandleMissingServletRequestParameterhandleServletRequestBindingExceptionhandleTypeMismatchhandleHttpMessageNotReadablehandleHttpMessageNotWritablehandleMethodArgumentNotValidExceptionhandleMissingServletRequestParameterhandleMissingServletRequestPartExceptionhandleBindException
ResponseStatusExceptionResolver
用來支持ResponseStatus的使用,處理使用了ResponseStatus註解的異常,根據註解的內容,返回相應的HTTP Status Code和內容給客戶端。如果Web應用程序中配置了ResponseStatusExceptionResolver,那麼我們就可以使用ResponseStatus註解來註解我們自己編寫的異常類,並在Controller中拋出該異常類,之後ResponseStatusExceptionResolver就會自動幫我們處理剩下的工作。
這是一個自己編寫的異常,用來表示訂單不存在:
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404 public class OrderNotFoundException extends RuntimeException { // ... }
這是一個使用該異常的Controller方法:
@RequestMapping(value="/orders/", 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的響應。
AnnotationMethodHandlerExceptionResolver和ExceptionHandlerExceptionResolver
用來支持ExceptionHandler註解,使用被ExceptionHandler註解所標記的方法來處理異常。其中AnnotationMethodHandlerExceptionResolver在3.0版本中開始提供,ExceptionHandlerExceptionResolver在3.1版本中開始提供,從3.2版本開始,Spring推薦使用ExceptionHandlerExceptionResolver。
如果配置了AnnotationMethodHandlerExceptionResolver和ExceptionHandlerExceptionResolver這兩個異常處理bean之一,那麼我們就可以使用ExceptionHandler註解來處理異常。
下面是幾個ExceptionHandler註解的使用例子:
@Controllerpublic 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() 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註解。
@ControllerAdviceclass 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; }}
SimpleMappingExceptionResolver
提供了將異常映射為視圖的能力,高度可定製化。其提供的能力有:
根據異常的類型,將異常映射到視圖;
可以為不符合處理條件沒有被處理的異常,指定一個默認的錯誤返回;
處理異常時,記錄log信息;
指定需要添加到Modle中的Exception屬性,從而在視圖中展示該屬性。
@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; } ...}
自定義ExceptionResolver
Spring MVC的異常處理非常的靈活,如果提供的ExceptionResolver類不能滿足使用,我們可以實現自己的異常處理類。可以通過繼承SimpleMappingExceptionResolver來定製Mapping的方式和能力,也可以直接繼承AbstractHandlerExceptionResolver來實現其它類型的異常處理類。
Spring MVC是如何創建和使用這些Resolver的?
首先看Spring MVC是怎麼載入異常處理bean的。
Spring MVC有兩種載入異常處理類的方式,一種是根據類型,這種情況下,會載入ApplicationContext下所有實現了ExceptionResolver介面的bean,並根據其order屬性排序,依次調用;一種是根據名字,這種情況下會載入ApplicationContext下,名字為handlerExceptionResolver的bean。
不管使用那種載入方式,如果在ApplicationContext中沒有找到異常處理bean,那麼Spring MVC會載入默認的異常處理bean。
默認的異常處理bean定義在DispatcherServlet.properties中。
以下代碼摘自ispatcherServlet,描述了異常處理類的載入過程:
/** * Initialize the HandlerMappings used by this class. *
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 matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList(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的。
Spring MVC把請求映射和處理過程放到try catch中,捕獲到異常後,使用異常處理bean進行處理。
所有異常處理bean按照order屬性排序,在處理過程中,遇到第一個成功處理異常的異常處理bean之後,不再調用後續的異常處理bean。
以下代碼摘自DispatcherServlet,描述了處理異常的過程。
何時該使用何種ExceptionResolver?
Spring提供了很多選擇和非常靈活的使用方式,下面是一些使用建議:
如果自定義異常類,考慮加上ResponseStatus註解;
對於沒有ResponseStatus註解的異常,可以通過使用ExceptionHandler+ControllerAdvice註解,或者通過配置SimpleMappingExceptionResolver,來為整個Web應用提供統一的異常處理。
如果應用中有些異常處理方式,只針對特定的Controller使用,那麼在這個Controller中使用ExceptionHandler註解。
不要使用過多的異常處理方式,不然的話,維護起來會很苦惱,因為異常的處理分散在很多不同的地方。
注意:由於文章字數有限.代碼只能截圖給大家看了。
點擊展開全文
※Java 小白比較關心的幾個問題
※畢業迷茫?java如何輕鬆高薪就業
※Java入門第二季6.1項目代碼
※一張圖搞定Java面向對象
※Java 的採用三種方法實現字元串逆序輸出
TAG:java學習吧 |
※SpringMVC常用註解標籤詳解
※Spring Cloud限流詳解
※Spring MVC之DispatcherServlet初始化詳解
※SpringMVC + security模塊 框架整合詳解
※透過現象看原理:詳解 Spring 中 Bean 的 this 調用導致 AOP 失效的原因
※Qorivva MPC56xx系列MCU的Flash加密解密原理與工程實現方法詳解
※詳解TogetherJS
※使用Wireshark詳解TCP協議
※iOS Airplay Screen Mirroring 同屏技術詳解
※MyBatis 配置 typeHandlers 詳解
※MySQL Explain詳解
※FPGA與ASIC的完美結合,Achronix Speedster 7t系列詳解
※AlphaGo之父DeepMind再出神作,PrediNet原理詳解
※HashMap詳解
※天維信通詳解AWS Direct Connect Gateway服務
※OpenStack之Magnum容器編服務排引擎詳解
※「詳解」WebSocket相關知識整理
※聚合查詢慢——詳解Global Ordinals與High Cardinality
※Spark調優的關鍵—RDD Cache緩存使用詳解
※VulnHub中LazySysAdmin 題目詳解