SpringMVC(15) - 處理異常
1. HandlerExceptionResolver
Spring HandlerExceptionResolver實現處理控制器執行期間發生的異常。 HandlerExceptionResolver有點類似於可以在Web應用程式描述web.xml中定義的異常對映。但是,它們提供了更靈活的方法。例如,它們提供有關在丟擲異常時正在執行哪個處理器的資訊。此外,處理異常的程式設計方式提供了更多選項,以便在請求轉發到另一個URL之前進行適當的響應(與使用Servlet特定異常對映時的最終結果相同)。
除了實現HandlerExceptionResolver介面,這只是實現 resolveException(Exception, Handler) 方法並返回ModelAndView的問題,還可以使用提供的SimpleMappingExceptionResolver或建立 @ExceptionHandler 方法。 SimpleMappingExceptionResolver可以獲取可能丟擲的任何異常的類名,並將其對映到檢視名稱。這在功能上等同於Servlet API中的異常對映功能,但也可以實現來自不同處理器的更精細的異常對映。另一方面,@ExceptionHandler註解還可用於處理異常的方法上。這些方法可以在@Controller中定義,也可以在@ControllerAdvice類中定義時以用來應用於更多@Controller類。
2. @ExceptionHandler
HandlerExceptionResolver介面和SimpleMappingExceptionResolver實現允許在轉發到這些檢視之前,以宣告方式將異常對映到特定檢視以及一些可選的Java邏輯。但是,在某些情況下,特別是在依賴@ResponseBody方法而不是檢視解析器時,直接設定響應的狀態並可選地將錯誤內容寫入響應體可能更方便。
可以使用@ExceptionHandler方法執行此操作。在控制器中宣告時,此類方法適用於該控制器(或其任何子類)的@RequestMapping方法引發的異常。還可以在@ControllerAdvice類中宣告@ExceptionHandler方法,在這種情況下,它會處理來自許多控制器的@RequestMapping方法的異常。下面是一個控制器本地@ExceptionHandler方法的示例:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
該異常可能與傳播的頂級異常(即丟擲直接IOException)或頂級包裝器異常中的直接原因(例如,包含在IllegalStateException內的IOException)相匹配。
對於匹配的異常型別,最好將目標異常宣告為方法引數,如上所示。 當多個異常方法匹配時,根異常匹配通常優先於原因異常匹配。 更具體地說,ExceptionDepthComparator用於根據丟擲的異常型別的深度對異常進行排序。
或者,可以將@ExceptionHandler值設定為異常型別陣列。 如果丟擲與列表中的某個型別的異常相匹配,則將呼叫使用匹配的@ExceptionHandler註釋的方法。 如果未設定註解值,則將使用宣告的方法引數型別進行匹配。
注:對於@ExceptionHandler方法,在特定控制器或建言器bean的處理器方法中,根異常匹配將優先於匹配當前異常的原因。但是,較高優先順序的@ControllerAdvice上的原因匹配仍然優先於較低優先順序的建言器bean上的任何匹配(無論是根異常還是原因級別)。因此,在使用多建議安排時,請在具有相應順序的優先順序建言器bean上宣告主根異常對映。
與使用@RequestMapping註解註釋的標準控制器方法非常相似,@ExceptionHandler方法的方法引數和返回值可以是靈活的。例如,可以在Servlet環境中訪問HttpServletRequest,在Portlet環境中訪問PortletRequest。返回型別可以是String,它被解釋為檢視名稱,ModelAndView物件,ResponseEntity,或者還可以新增@ResponseBody以使用訊息轉換器轉換方法返回值並將其寫入響應流。
最後,@ExceptionHandler方法實現可以選擇通過以原始形式重新丟擲它來退出處理給定的異常例項。這在只對根級別匹配或在無法靜態確定的特定上下文中的匹配中感興趣的情況下非常有用。重新丟擲的異常將通過其餘的解析鏈傳播,就像給定的@ExceptionHandler方法一開始沒有匹配一樣。
3. 處理標準的Spring MVC異常
Spring MVC在處理請求時可能會引發許多異常。 SimpleMappingExceptionResolver可以根據需要輕鬆地將任何異常對映到預設錯誤檢視。但是,在與以自動方式解釋響應的客戶端一起工作時,需要在響應上設定特定的狀態程式碼。根據引發的異常,狀態程式碼可能指示客戶端錯誤(4xx)或伺服器錯誤(5xx)。
DefaultHandlerExceptionResolver將Spring MVC異常轉換為特定的錯誤狀態程式碼。它預設註冊了MVC名稱空間,MVC Java配置,以及DispatcherServlet(即不使用MVC名稱空間或Java配置時)。下面列出了此解析程式處理的一些例外情況以及相應的狀態程式碼:
Exception | HTTP Status Code |
---|---|
|
400 (Bad Request) |
|
500 (Internal Server Error) |
|
406 (Not Acceptable) |
|
415 (Unsupported Media Type) |
|
400 (Bad Request) |
|
500 (Internal Server Error) |
|
405 (Method Not Allowed) |
|
400 (Bad Request) |
|
500 (Internal Server Error) |
|
400 (Bad Request) |
|
400 (Bad Request) |
|
404 (Not Found) |
|
404 (Not Found) |
|
400 (Bad Request) |
DefaultHandlerExceptionResolver通過設定響應的狀態透明地工作。但是,如果應用程式需要在每個錯誤響應中新增開發人員友好的內容,例如在提供REST API時,它就不會將任何錯誤內容寫入響應主體。可以通過檢視解析器準備ModelAndView並呈現錯誤內容 --- 即通過配置ContentNegotiatingViewResolver,MappingJackson2JsonView等。但是,可能更喜歡使用@ExceptionHandler方法。
如果更喜歡通過@ExceptionHandler方法編寫錯誤內容,則可以擴充套件ResponseEntityExceptionHandler。這是@ControllerAdvice類的一個方便的基礎,它提供了一個@ExceptionHandler方法來處理標準的Spring MVC異常並返回ResponseEntity。這允許使用訊息轉換器自定義響應並寫入錯誤內容。
4. 使用@ResponseStatus註釋業務異常
可以使用@ResponseStatus註釋業務異常。引發異常時,ResponseStatusExceptionResolver通過相應地設定響應的狀態來處理它。預設情況下,DispatcherServlet註冊ResponseStatusExceptionResolver並且可以使用它。
5. 自定義預設Servlet容器錯誤頁面
當響應的狀態設定為錯誤狀態程式碼並且響應體為空時,Servlet容器通常會呈現HTML格式的錯誤頁面。要自定義容器的預設錯誤頁面,可以在web.xml中宣告<error-page>元素。直到Servlet 3,該元素必須對映到特定的狀態程式碼或異常型別。從Servlet 3開始,不需要對映錯誤頁面,這實際上意味著指定的位置自定義預設的Servlet容器錯誤頁面。
<error-page>
<location>/error</location>
</error-page>
請注意,錯誤頁面的實際位置可以是JSP頁面或容器中的其他URL,包括通過@Controller方法處理的URL:
在編寫錯誤資訊時,可以通過控制器中的請求屬性訪問HttpServletResponse上設定的狀態程式碼和錯誤訊息:
@Controller
public class ErrorController {
@RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
或者在JSP中:
<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
reason:<%=request.getAttribute("javax.servlet.error.message") %>
}