一、Spring Boot默认的异常处理机制
1:浏览器默认返回效果
2:原理解析
为了便于源码跟踪解析,在·Controller中手动设置异常。
@RequestMapping(value="/emps",method=RequestMethod.GET) public String emps(Map<String,Object> map){ System.out.println("emp list ......."); Collection<Employee> emps=employeeDao.getAll(); map.put("emps", emps); int i=1/0;//设置异常 return "list"; }
浏览器访问该请求 http://localhost:8080/crud/emps,后台进入DispatcherServlet。
1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 HttpServletRequest processedRequest = request; 3 HandlerExecutionChain mappedHandler = null; 4 boolean multipartRequestParsed = false; 5 6 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 7 8 try { 9 ModelAndView mv = null; 10 Exception dispatchException = null; 11 12 try { 13 processedRequest = checkMultipart(request); 14 multipartRequestParsed = (processedRequest != request); 15 16 // Determine handler for the current request. 17 mappedHandler = getHandler(processedRequest); 18 if (mappedHandler == null || mappedHandler.getHandler() == null) { 19 noHandlerFound(processedRequest, response); 20 return; 21 } 22 23 // Determine handler adapter for the current request. 24 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 25 26 // Process last-modified header, if supported by the handler. 27 String method = request.getMethod(); 28 boolean isGet = "GET".equals(method); 29 if (isGet || "HEAD".equals(method)) { 30 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 31 if (logger.isDebugEnabled()) { 32 logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); 33 } 34 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 35 return; 36 } 37 } 38 39 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 40 return; 41 } 42 43 // Actually invoke the handler. 44 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 45 46 if (asyncManager.isConcurrentHandlingStarted()) { 47 return; 48 } 49 50 applyDefaultViewName(processedRequest, mv); 51 mappedHandler.applyPostHandle(processedRequest, response, mv); 52 } 53 catch (Exception ex) { 54 dispatchException = ex; 55 } 56 catch (Throwable err) { 57 // As of 4.3, we're processing Errors thrown from handler methods as well, 58 // making them available for @ExceptionHandler methods and other scenarios. 59 dispatchException = new NestedServletException("Handler dispatch failed", err); 60 } 61 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 62 } 63 catch (Exception ex) { 64 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); 65 } 66 catch (Throwable err) { 67 triggerAfterCompletion(processedRequest, response, mappedHandler, 68 new NestedServletException("Handler processing failed", err)); 69 } 70 finally { 71 if (asyncManager.isConcurrentHandlingStarted()) { 72 // Instead of postHandle and afterCompletion 73 if (mappedHandler != null) { 74 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); 75 } 76 } 77 else { 78 // Clean up any resources used by a multipart request. 79 if (multipartRequestParsed) { 80 cleanupMultipart(processedRequest); 81 } 82 } 83 } 84 }
第17行获取HandlerExecutionChain,根据url:/emps 从RequestMappingHandlerMapping获取HandlerExecutionChain
第24行根据Handler获取相应的RequestMappingHandlerAdapter
第44行RequestMappingHandlerAdapter调用handle(),其中完成Cnotroller.emps()调用
1)argumentResolvers根据HandlerMethod.parameters 完成emps方法入参的准备
2)执行InvocableHandlerMethod.doInvoke;
3)returnValueHandlers.handleReturnValue对Cnotroller.emps() 返回参数的对应处理
由于在emps()手动设置了异常,上述第2步调用InvocableHandlerMethod.doInvoke时会抛出 java.lang.ArithmeticException: / by zero
程序执行进入54行,然后执行61行:processDispatchResult。
1222行由HandlerExceptionResolver对异常进行处理.其中DefaultErrorAttributes的exception处理:
最后1244 行抛出异常,然后一层一层往外抛出此异常,直到最外层StandardWrapperValve.invoke()对此异常进行了处理
然后程序执行到StandardHostValve的173行,判断是否需要进行response的异常处理
StandardHostValve中status()中,229行会从容器中配置的ErrorPage:转发后默认处理的ErrorController
StandardHostValve.custom()中 根据errorPage.location进行转发。
SpringBoot中BasicErrorController:处理默认异常转发的/error请求
1 @Controller 2 @RequestMapping("${server.error.path:${error.path:/error}}") 3 public class BasicErrorController extends AbstractErrorController { 4 @RequestMapping(produces = "text/html") //产生html类型的数据;浏览器发送的请求来到这个方法处理 5 public ModelAndView errorHtml(HttpServletRequest request, 6 HttpServletResponse response) { 7 HttpStatus status = getStatus(request); 8 Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes( 9 request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); 10 response.setStatus(status.value()); 11 ModelAndView modelAndView = resolveErrorView(request, response, status, model);//去哪个页面作为错误页面;包含页面地址和页面内容 12 return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); 13 } 14 15 @RequestMapping 16 @ResponseBody 17 public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {//产生json数据,其他客户端来到这个方法处理; 18 Map<String, Object> body = getErrorAttributes(request, 19 isIncludeStackTrace(request, MediaType.ALL)); 20 HttpStatus status = getStatus(request); 21 return new ResponseEntity<Map<String, Object>>(body, status); 22 }
DefaultErrorAttributes中设置页面的共享信息
@Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, requestAttributes); addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes; }
DefaultErrorViewResolver
@Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName;//默认SpringBoot可以去找到一个页面? error/500 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext);//模板引擎可以解析这个页面地址就用模板引擎解析 if (provider != null) { return new ModelAndView(errorViewName, model);//模板引擎可用的情况下返回到errorViewName指定的视图地址 } return resolveResource(errorViewName, model);//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/500.html }