作者:fredalxin
地址:https://fredal.xin/400-error-deal
很多人都會在平時開發過程中遇到400或500異常,並且也沒有走到服務端controller中,就變得有些不知所措。
我們知道SpringMVC從DispatchServlet開始接收與分發請求,從入口開始debug,還能找不到問題所在么?
從DispatchServlet的doDispatch()方法開始處理請求:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//刪除一些代碼
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 刪除一些代碼
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex; // 這里捕獲了異常TypeMismatchException
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
}
finally {
// 刪除一些代碼
}
}
其實在這兒我們就能看到exception的具體異常棧,有興趣的可以繼續看springMVC的處理方法processDispatchResult。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);// 執行這個方法
errorView = (mv != null);
}
}
// 方便閱讀,刪除了其他代碼
}
這個方法中對異常進行判斷,發現不是“ModelAndViewDefiningException”就交給processHandlerException方法繼續處理。
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;
}
}
// 去掉了一些代碼
throw ex;
}
這里看到for循環來找一個handlerExceptionResolver來處理這個異常。handler列表有spring自帶的ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver以及自定義的exceptionResolver。
這些都繼承自AbstractHandlerExceptionResolver類,這個類是一個抽象類,它實現了HandlerExceptionResolver接口,它對HandlerExceptionResolver接口約定的方法的所實現代碼如下:
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
logException(ex, request);
prepareResponse(ex, response);
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
首先判斷當前異常處理器是否可以處理當前的目標handler。例如通過for循環依次發現輪到DefaultHandlerExceptionResolver才能處理,那么最終會執行該handlerExceptionResolver的doResolveException方法。
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod(...);
}
// 刪除部分else if instanceof 判斷
else if (ex instanceof TypeMismatchException) {
// 執行到了這里
return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
}
// 刪除部分else if instanceof 判斷
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
}
return null;
}
通過對異常類型的判斷,來執行相應handleXXXException方法。而handleXXXException方法中,有很多是會拋出400錯誤的!
舉個幾個栗子:
protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(400, ex.getMessage());
return new ModelAndView();
}
protected ModelAndView handleServletRequestBindingException(ServletRequestBindingException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(400, ex.getMessage());
return new ModelAndView();
}
protected ModelAndView handleTypeMismatch(TypeMismatchException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(400);
return new ModelAndView();
}
protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(400);
return new ModelAndView();
}
protected ModelAndView handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(400);
return new ModelAndView();
}
protected ModelAndView handleMissingServletRequestPartException(MissingServletRequestPartException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(400, ex.getMessage());
return new ModelAndView();
}
protected ModelAndView handleBindException(BindException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(400);
return new ModelAndView();
}
那么拋出400錯誤的時候該怎么解決呢?
從服務端角度出發,可以定義完善的全局異常處理器exceptionHandler,把易拋出400的錯誤例如TypeMismatchException、BindException都給處理掉,返回能看得懂的信息。
從客戶端請求過程中來看,可以自定義handlerExceptionResolver,只需實現HandlerExceptionResolver接口即可,例如:
public class ApiHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception exception) {
ModelAndView model = new ModelAndView();
// do something ...
return model;
}
}
所以遇到400錯誤的時候不要慌,畢竟400它是個標准的錯誤碼,好好debug或者查閱一下相關資料便能迎刃而解。
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!
3.阿里 Mock 工具正式開源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式發布,全新顛覆性版本!
覺得不錯,別忘了隨手點贊+轉發哦!