Spring之webMvc異常處理
異常處理可以前端處理,也可以後端處理。
從穩妥的角度出發,兩邊都應該進行處理。
本文專門闡述如何在服務端進行http請求異常處理。
一、常見的異常型別
當我們做http請求的時候,會有各種各樣的可能錯誤,比較常見的例如:
1.服務類異常
2.介面異常,而介面異常有各種各樣的情況
究極就是介面的異常。
異常可以發生在請求的各個步驟之中,這裡主要闡述網路通訊正常的情況。
這些通訊異常在方法 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException(HttpServletRequest, HttpServletResponse, Object, Exception)中
有詳細的描述:
if (ex instanceof HttpRequestMethodNotSupportedException) { return handleHttpRequestMethodNotSupported( (HttpRequestMethodNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotSupportedException) { return handleHttpMediaTypeNotSupported( (HttpMediaTypeNotSupportedException) ex, request, response, handler); }else if (ex instanceof HttpMediaTypeNotAcceptableException) { return handleHttpMediaTypeNotAcceptable( (HttpMediaTypeNotAcceptableException) ex, request, response, handler); } else if (ex instanceof MissingPathVariableException) { return handleMissingPathVariable( (MissingPathVariableException) ex, request, response, handler); }else if (ex instanceof MissingServletRequestParameterException) { return handleMissingServletRequestParameter( (MissingServletRequestParameterException) ex, request, response, handler); } else if (ex instanceof ServletRequestBindingException) { return handleServletRequestBindingException( (ServletRequestBindingException) ex, request, response, handler); } else if (ex instanceof ConversionNotSupportedException) { return handleConversionNotSupported( (ConversionNotSupportedException) ex, request, response, handler); } else if (ex instanceof TypeMismatchException) { return handleTypeMismatch( (TypeMismatchException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotReadableException) { return handleHttpMessageNotReadable( (HttpMessageNotReadableException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotWritableException) { return handleHttpMessageNotWritable( (HttpMessageNotWritableException) ex, request, response, handler); } else if (ex instanceof MethodArgumentNotValidException) { return handleMethodArgumentNotValidException( (MethodArgumentNotValidException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestPartException) { return handleMissingServletRequestPartException( (MissingServletRequestPartException) ex, request, response, handler); } else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } else if (ex instanceof NoHandlerFoundException) { return handleNoHandlerFoundException( (NoHandlerFoundException) ex, request, response, handler); } else if (ex instanceof AsyncRequestTimeoutException) { return handleAsyncRequestTimeoutException( (AsyncRequestTimeoutException) ex, request, response, handler); }
我們整理為表格:
編碼 | 名稱 | 備註 |
handleHttpRequestMethodNotSupported | 請求方法不支援 | 例如RequestMapping只指定了post,但用get請求 |
HttpMediaTypeNotSupportedException | 媒體型別不支援 | 例如訊息轉換器可能只處理JSON,但請求頭確設定為xml之類的 |
HttpMediaTypeNotAcceptableException | 媒體型別不可接受 | 請求處理器無法生成客戶端可以支援的響應內容 |
MissingPathVariableException | 缺失路徑變數 |
請求url缺乏路徑變數。例如 @RequestMapping("/show/{name}") public Object show(@PathVariable String name){} 工程師可能少寫了{name} |
MissingServletRequestParameterException | 缺失Servlet請求引數 | 例如使用了@RequstParam,但是url並沒有提供對應的引數 |
ServletRequestBindingException | Servlet請求繫結異常 | 在執行繫結的時候發生的異常 |
ConversionNotSupportedException | 不支援的轉換異常 | 無法為bean找到匹配的編輯器或者轉換器 |
TypeMismatchException | 型別不匹配異常 | 給bean複製的時候,發現型別不匹配 |
HttpMessageNotReadableException | http訊息無法讀取 | 當HttpMessageConverter#read方法發生錯誤的時候 |
HttpMessageNotWritableException | http訊息無法寫異常 | 當HttpMessageConverter#write方法發生錯誤的時候 |
MethodArgumentNotValidException | 方法引數不可用異常 | 驗證 @Valid過的引數時候所發生的異常
|
MissingServletRequestPartException | 缺失Servlet請求part異常 | 請求的媒體型別是檔案型別(或者是附件),但內容沒有 |
BindException | 繫結異常 | |
NoHandlerFoundException | 沒有找到處理器 |
By default when the DispatcherServlet can't find a handler for a request it sends a 404 response. However if its property "throwExceptionIfNoHandlerFound" is set to 預設情況下,分發器處理器程式無法找到請求的處理器的時候(例如靜態資源或者請求控制器方法的時候),會發送一個404響應。此外會設定屬性throwExceptionIfNoHandlerFound=true,並引發沒有處理器異常。使用者可以考慮配置一個HandlerExceptionResolver來處理 |
AsyncRequestTimeoutException | 非同步請求超時異常。即503 錯誤。 |
spring和springboot中和異常/錯誤有關的類遠不止上表那麼多。
出於程式碼的嚴謹性要求,幾乎所有的函式/過程都會有異常處理(try catch throw),限於篇幅,不會討論所有的程式碼,也沒有必要。
二、WMS中的異常配置
關於wms的介紹,可以參考: https://www.cnblogs.com/lzfhope/p/16103174.html
wms可以處理異常的地方有幾個,但我們專門考慮exception字眼的部分。
@Bean public HandlerExceptionResolver handlerExceptionResolver( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>(); configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager); } extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; } /** * Override this method to configure the list of * {@link HandlerExceptionResolver HandlerExceptionResolvers} to use. * <p>Adding resolvers to the list turns off the default resolvers that would otherwise * be registered by default. Also see {@link #addDefaultHandlerExceptionResolvers} * that can be used to add the default exception resolvers. * @param exceptionResolvers a list to add exception resolvers to (initially an empty list) */ protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { } /** * A method available to subclasses for adding default * {@link HandlerExceptionResolver HandlerExceptionResolvers}. * <p>Adds the following exception resolvers: * <ul> * <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through * {@link org.springframework.web.bind.annotation.ExceptionHandler} methods. * <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with * {@link org.springframework.web.bind.annotation.ResponseStatus}. * <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring exception types * </ul> */ protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers, ContentNegotiationManager mvcContentNegotiationManager) { ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver(); exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager); exceptionHandlerResolver.setMessageConverters(getMessageConverters()); exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers()); exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { exceptionHandlerResolver.setResponseBodyAdvice( Collections.singletonList(new JsonViewResponseBodyAdvice())); } if (this.applicationContext != null) { exceptionHandlerResolver.setApplicationContext(this.applicationContext); } exceptionHandlerResolver.afterPropertiesSet(); exceptionResolvers.add(exceptionHandlerResolver); ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver(); responseStatusResolver.setMessageSource(this.applicationContext); exceptionResolvers.add(responseStatusResolver); exceptionResolvers.add(new DefaultHandlerExceptionResolver()); }
通過wms機制,可以覆蓋bean(handlerExceptionResolver),或者覆蓋方法 configureHandlerExceptionResolvers新增異常處理解析器。
不過一般情況下,我們都沒有那個必要。
一般情況下,使用DefaultHandlerExceptionResolver進行處理即可,即前文介紹的內容。
三、springboot的http請求異常
如果使用springboot開發,那麼springboot會通過自動配置引入一個 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
這類控制器類實現了介面 org.springframework.boot.web.servlet.error.ErrorController。
根據springboot的有關描述,我們可以實現它介面org.springframework.boot.web.servlet.error.ErrorController,以便覆蓋預設的實現。
例如:
@Controller public class ErrController implements ErrorController { @RequestMapping("/error") public String handleError(HttpServletRequest request,HttpServletResponse response) { int statusCode=response.getStatus(); if (statusCode == 404) { return "/main/err.html"; } else if (statusCode == 444) { return "/main/login.html"; } return ""; } }
需要注意的是,ErrorController 的程式碼是在wms的異常處理之後執行的。
此外,也可以只用控制器advice註解處理, 例如:SpringBoot系列——自定義統一異常處理 - huanzi-qch - 部落格園 (cnblogs.com)
四、小結
spring為了少讓我們的程式碼出現問題,還引入了一個機制:驗證。例如可以驗證引數驗證。不過驗證僅僅只是解決了少數的異常情形,雖然引數不合法的發生的數量還是比較多的。
但這個驗證一般情況下,也不需要,因為一般在客戶端就先處理了!