Spring MVC提供了幾種異常處理的方法:
- 對於每個異常類的處理
- 對於每個@Controller類的異常處理
- 對於所有@Controller類的全局異常處理
基於@ResponseStatus的異常處理
通常,處理Web請求時引發的任何未處理的異常都會導致服務器返回HTTP 500響應。 但是,您自己編寫的任何自定義異常都可以使用@ResponseStatus註釋進行註釋。 當一個帶註釋的異常從控制器方法中拋出,並且沒有在其他地方處理時,它會自動返回使用指定的狀態碼的HTTP響應。下面是一個示例,當找不到指定的訂單時,拋出訂單未找到的異常。
//定義一個@ResponseStatus註釋的異常類
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}
//定義一個訂單Controller類
@Controller
public class OrderController{
@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";
}
}
上面的實例中,當訪問一個不存在的訂單時,將返回一個HTTP 404錯誤頁面。
基於Controller的異常處理
通過在Controller類中單獨添加一個@ExceptionHandler註釋的方法可以處理該Controller類中@RequestMapping註解的方法處理時拋出的異常。@ExceptionHandler註釋的方法可以:
- 不帶@ResponseStatus註釋的異常(比如系統自帶的異常類)
- 將用戶引導到專門的錯誤頁面
- 創建一個總體的自定義錯誤響應
@Controller
public class ExceptionHandlingController {
// @RequestMapping methods
...
// Exception handling methods
// 將指定異常轉換爲對應的HTTP狀態碼
@ResponseStatus(value=HttpStatus.CONFLICT,reason="Data integrity violation") // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void conflict() {
// Nothing to do
}
// 指定用於顯示指定異常的view名稱
@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";
}
// 總體控制-將以上具體異常類之外的異常引導到對應錯誤頁. Or
// consider subclassing ExceptionHandlerExceptionResolver (see below).
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
上面的實例中,如果@Controller類的@RequestMapping方法執行時拋出了異常,則會觸發@Controller類中異常處理方法。
如果是DataIntegrityViolationException異常,則返回409錯誤;
如果是SQLException或DataAccessException,則跳轉到databaseError頁面;
否則的話,將跳轉到error頁面。
小技巧
由於在跳轉到的錯誤頁中,普通用戶是不希望看到具體的JAVA錯誤詳情和代碼調用堆棧的,但是對於程序開發者來說這些信息卻非常有用,因此,可以通過將這些信息轉化爲頁面的註釋不失爲一個兩全其美的解決方案。
<!--JSP-->
<h1>Error Page</h1>
<p>Application has encountered an error. Please contact support on ...</p>
<!--
Failed URL: ${url}
Exception: ${exception.message}
<c:forEach items="${exception.stackTrace}" var="ste"> ${ste}
</c:forEach>
-->
如果開發者要了解異常詳情的話,可以通過瀏覽HTML源碼來查看。
全局異常處理-使用@ControllerAdvice
雖然上面介紹的兩種異常處理方式都是很好的工作,但是使用@ControllerAdvice的全局異常處理方式更加優雅,它可以處理整個應用程序中所有@Controller類的異常,從而將異常處理邏輯從業務代碼中分離出來。
所有使用@ControllerAdvice註解的類都可以變成Controller切面,該類中@ExceptionHandler, @InitBinder, @ModelAttribute註解方法可以在所有的@Controller類中共享。
- @ExceptionHandler註解的方法用於異常處理。
- @InitBinder註解的方法用於handler方法參數從web請求到java bean的數據綁定。
- @ModelAttribute註解的方法用於增強Model。
下面是全局異常示例:
@ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
@ResponseStatus(HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
@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;
}
}
HandlerExceptionResolver
DispatcherServlet的應用程序上下文中任何實現HandlerExceptionResolver的類都能攔截和處理Spring MVC系統拋出的並且@Controller類未處理的異常。
SpringMVC默認創建三個異常解析器。
- ExceptionHandlerExceptionResolver匹配@Controller和@ControllerAdvice註解的handler類中的@ExceptionHandler方法。
- ResponseStatusExceptionResolver查找@ResponseStatus註解的未捕獲異常 。
- DefaultHandlerExceptionResolver將標準的Spring異常轉化爲HTTP狀態碼。
SimpleMappingExceptionResolver
Spring很早就提供了一個簡單但方便的HandlerExceptionResolver實現,你可能已經發現它已經被你的應用程序使用過了 - SimpleMappingExceptionResolver。它提供一下功能:
- 建立異常類和view名稱的映射。
- 爲其他地方未處理異常指定一個默認的錯誤頁。
- 記錄日誌消息。
- 通過給異常屬性設置名稱將其添加到Model中,這樣就能在view中使用。
@Configuration
@EnableWebMvc // Optionally setup Spring MVC defaults (if you aren't using
// Spring Boot & haven't specified @EnableWebMvc elsewhere)
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); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
...
}