• SpringBoot exception异常处理机制源码解析


    一、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
        }
  • 相关阅读:
    C++中虚函数
    ES6入门四:对象字面量扩展与字符串模板字面量
    ES6入门三:解构
    ES6入门二:默认值与默认值表达式
    ES6入门一:块级作用域(let&const)、spread展开、rest收集
    JavaScript严格模式
    JavaScript中with不推荐使用,为什么总是出现在面试题中?
    ES6入门一:ES6简介及Babel转码器
    HTML5之websocket
    HTML5之fileReader异步读取文件及文件切片读取
  • 原文地址:https://www.cnblogs.com/xiaoliangup/p/10392312.html
Copyright © 2020-2023  润新知