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
    }
posted @ 2019-02-17 19:20  小亮的BLOG  阅读(1020)  评论(0编辑  收藏  举报