Spring MVC 底层源码解读

实例流程图

在这里插入图片描述

组成

  • DispatcherServlet:HTTP请求处理程序/控制器的中央分配器,Spring MVC 项目的入口。

  • HandlerMapping:处理器映射

    根据 request 找到请求对应的 HandlerExecutionChain

    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
    
  • HandlerExecutionChain:处理器执行链

    • 包含了对 Controller 进行包装的处理器 handler(一般是 HandlerMethod 对象)

      • HandlerMethod:封装由方法和bean组成的处理程序方法的信息。提供对方法参数、方法返回值、方法注释等的方便访问。其中的方法在后续使用 InvocableHandlerMethod 的 doInvoke 方法进行反射的时候会被调用到。
    • 包含了拦截器数组和拦截器列表,配合拦截器的 preHandle / postHandle / afterCompletion 可以对 handler 进行前置处理、后置处理、资源清理等增强操作。

      • preHandle

        调用时间:Controller方法处理之前

        执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序一个接一个执行

        若返回false,则中断执行,注意:不会进入afterCompletion

        扫描二维码关注公众号,回复: 8699301 查看本文章
      • postHandle

        调用前提:preHandle返回true

        调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作

        执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序倒着执行

        备注:postHandle虽然post打头,但post、get方法都能处理

      • afterCompletion

        调用前提:preHandle返回true

        调用时间:DispatcherServlet进行视图的渲染之后

  • HandlerAdapter:处理器适配器

    • 其中最重要的 handle 方法可以返回一个 ModelAndView 对象。
    • supports 方法可以判断是否支持传入的处理器类型,如 HandlerMethod 等。
  • ModelAndView:这个类只是同时持有模型和视图,使控制器能够在一个返回值中同时返回模型和视图。

  • ViewResolver:视图解析器,其resolveViewName 方法会根据 viewName 解析出相应的 View 类型,例如是 ThymeleafView 或 JSP View.

新建项目

本文目的在于通过一个简单的 Spring Boot 案例来解读 Spring MVC 底层源码。

  • 在 IntelliJ IDEA 中基于 Spring Boot + Spring Web + Thymeleaf+ Lombok 新建一个项目。

  • model 包下创建Person

    @Data
    @AllArgsConstructor
    public class Person {
    
        private String name;
    
        private Integer age;
    }
    
  • controller 包下创建PersonController

    @Controller
    @RequestMapping("persons")
    public class PersonController {
    
        @GetMapping("")
        public ModelAndView getPersons() {
            List<Person> persons = new ArrayList<>();
            persons.add(new Person("Jake", 27));
            persons.add(new Person("Heather", 27));
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("persons", persons);
            modelAndView.setViewName("person");
            return modelAndView;
        }
    
        @GetMapping("json")
        @ResponseBody
        public List<Person> getPersonsJson() {
            List<Person> persons = new ArrayList<>();
            persons.add(new Person("Jake", 27));
            persons.add(new Person("Heather", 27));
            return persons;
        }
    }
    
  • resources/templates 路径下创建person.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>人员</title>
    </head>
    <body>
        <table>
            <thead>
                <tr>
                    <th>姓名</th>
                    <th>年龄</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="person:${persons}">
                    <td th:text="${person.name}"></td>
                    <td th:text="${person.age}"></td>
                </tr>
            </tbody>
        </table>
    </body>
    </html>
    

源码详解

ModelAndView

这种情况是在控制层返回ModelAndView对象,会在前端看到渲染的视图。

  • 发起请求 http://localhost:8080/persons

  • 入口DispatcherServletdoService(HttpServletRequest request, HttpServletResponse response)

  • 请求分发doDispatch(HttpServletRequest request, HttpServletResponse response)

  • 根据请求获取映射处理器HandlerExecutionChain mappedHandler = getHandler(processedRequest);

    • handlerMappings中找到以当前 request 作为 key 的 handlerMapping

      protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
          if (this.handlerMappings != null) {
              for (HandlerMapping mapping : this.handlerMappings) {
                  HandlerExecutionChain handler = mapping.getHandler(request);
                  if (handler != null) {
                      return handler;
                  }
              }
          }
          return null;
      }
      
    • 由 Debug 模式看出 handlerMappings中包含的元素如下:

      handlerMappings = {ArrayList@5507}  size = 5
       0 = {RequestMappingHandlerMapping@6466} 
       1 = {BeanNameUrlHandlerMapping@6467} 
       2 = {RouterFunctionMapping@6468} 
       3 = {SimpleUrlHandlerMapping@6469} 
       4 = {WelcomePageHandlerMapping@6470} 
      
    • 对于当前请求,进入if(handler != null)判断时,对应的 mapping 和 handler 分别是:

      mapping = {RequestMappingHandlerMapping@6466} 
      
      handler = {HandlerExecutionChain@6534} "HandlerExecutionChain with [com.jake.spring.mvc.controller.PersonController#getPersons()] and 2 interceptors"
       handler = {HandlerMethod@6544} "com.jake.spring.mvc.controller.PersonController#getPersons()"
       interceptors = null
       interceptorList = {ArrayList@6545}  size = 2
        0 = {ConversionServiceExposingInterceptor@5577} 
        1 = {ResourceUrlProviderExposingInterceptor@5578} 
       interceptorIndex = -1
      

      说明此时使用的HandlerMappingRequestMappingHandlerMapping,其对应的handler是一个 HandlerExecution 对象,该对象中还有一个HandlerMethod类型的handler

  • HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    • getHandlerAdapter方法

      protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
          if (this.handlerAdapters != null) {
              for (HandlerAdapter adapter : this.handlerAdapters) {
                  if (adapter.supports(handler)) {
                      return adapter;
                  }
              }
          }
          throw new ServletException("No adapter for handler [" + handler +
                                     "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
      }
      
    • 由 Debug 可知, handler,即mappedHandler.getHandler() 的值是一个 HandlerMethod对象

      handler = {HandlerMethod@6544}com.jake.spring.mvc.controller.PersonController#getPersons()
      
    • 此时的 this.handlerAdapters为:

      this.handlerAdapters = {ArrayList@5508}  size = 4
       0 = {RequestMappingHandlerAdapter@5604} 
       1 = {HandlerFunctionAdapter@5605} 
       2 = {HttpRequestHandlerAdapter@5606} 
       3 = {SimpleControllerHandlerAdapter@5607} 
      
    • 在进入if (adapter.supports(handler))时,得到的adapter

      adapter = {RequestMappingHandlerAdapter@5604}
      

      这里的 supports 方法其实就是一个类型判断,以HttpRequestHandlerAdapter为例:

      @Override
      public boolean supports(Object handler) {
          return (handler instanceof HttpRequestHandler);
      }
      
  • 语句执行完成后,ha = {RequestMappingHandlerAdapter@5569}

  • 判断是否要应用HandlerExecutionChain的变量 mappedHandler 的前置处理器,如果不应用直接返回。

    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
       return;
    }
    

    HandlerExecutionChainapplyPreHandle方法

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }
    
  • 调用mappedHandlerhandler

    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    

    进入AbstractHandlerMethodAdapterhandle方法:

    @Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }
    

    由 Debug 可知,handler=com.jake.spring.mvc.controller.PersonController#getPersons()

  • 此方法会调用当前类对象的handleInternal方法,而handleInternal方法只有一个类RequestMappingHandlerAdapter实现,其又调用了invokeHandlerMethod方法。

    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    
  • 进入InvocableHandlerMethodinvokeAndHandle方法,此方法又调用了doInvoke方法,这个方法中真正实现了反射操作。

  • 进入PersonControllergetPersons方法,封装好ModelAndView对象并返回

  • 再次回到DispatcherServletdoDispatch方法,此时

    mv = {ModelAndView@6338} "ModelAndView [view="person"; model={persons=[Person(name=Jake, age=27), Person(name=Heather, age=27)]}]"
    
  • 调用applyDefaultViewName给没有viewNamerequest分配一个默认viewName

  • 调用mappedHandler.applyPostHandle(processedRequest, response, mv)做后置处理

    HandlerExecutionChainapplyPostHandle方法

    /**
     * Apply postHandle methods of registered interceptors.
     */
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = interceptors.length - 1; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }
    

    postHandle 方法,顾名思义就是在当前请求进行处理之后,也就是 Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。

  • 调用processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)

    /**
     * Handle the result of handler selection and handler invocation, which is
     * either a ModelAndView or an Exception to be resolved to a ModelAndView.
     */
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                       @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                       @Nullable 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);
            }
        }
    
        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace("No view rendering, null ModelAndView returned.");
            }
        }
    
        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Concurrent handling started during a forward
            return;
        }
    
        if (mappedHandler != null) {
            // Exception (if any) is already handled..
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }
    
  • 调用本类(DispatcherServlet)的render方法,其作用是渲染传入的ModelAndView对象

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Determine locale for request and apply it to the response.
        Locale locale =
            (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
        response.setLocale(locale);
    
        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            // We need to resolve the view name.
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                                           "' in servlet with name '" + getServletName() + "'");
            }
        }
        else {
            // No need to lookup: the ModelAndView object contains the actual View object.
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                                           "View object in servlet with name '" + getServletName() + "'");
            }
        }
    
        // Delegate to the View object for rendering.
        if (logger.isTraceEnabled()) {
            logger.trace("Rendering view [" + view + "] ");
        }
        try {
            if (mv.getStatus() != null) {
                response.setStatus(mv.getStatus().value());
            }
            view.render(mv.getModelInternal(), request, response);
        }
        catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Error rendering view [" + view + "]", ex);
            }
            throw ex;
        }
    }
    
  • 调用本类中的 resolveViewName 方法得到相应的view

    @Nullable
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                                   Locale locale, HttpServletRequest request) throws Exception {
    
        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }
    

    在进入if(view != null)时,Debug 中观察到的变量如下:

    viewName = "person"
    model = {ModelMap@6527}  size = 1
     "persons" -> {ArrayList@6492}  size = 2
    locale = {Locale@6428} "zh_CN"
    request = {RequestFacade@5508} 
    viewResolver = {ContentNegotiatingViewResolver@6540} 
    view = {ThymeleafView@6541} 
    

    这里使用的ViewResolver对象是ContentNegotiatingViewResolver类型的,另外,由于我们当前采用的是 Thymeleaf框架,所以View对象的实现类是ThymeleafView,该类是thymeleaf-spring5.jar中的一个类。再调用 view 的 render 方法,呈现指定模型的视图,此方法的第一步是准备请求,以渲染 JSP 为例,这意味着将模型对象设置为请求属性;第二步是视图的实际呈现,例如通过 RequestDispatcher 包含 JSP。

  • 渲染完成后,断点回到了processDispatchResult,执行mappedHandler.triggerAfterCompletion(request, response, null)来进行资源清理。

  • 如果在doDispatch时捕获异常或错误,也会触发HandlerInterceptorafterCompletion方法来进行资源清理。

  • 经过上述一系列步骤后,在浏览器中可以看到

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zSkOVIfl-1579336760673)(img/Spring MVC/person_html.png)]

ResponseBody

这种情况直接返回 JSON 数据给前端

  • 访问 http://localhost:8080/persons/json

  • 这种情况 doDispatch中的ModelAndView对象mvnull,其他均与返回视图的情况一样。

  • 在浏览器中可以看到

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7oDovfq-1579336760674)(img/Spring MVC/person_json.png)]

过程总结

可以将上述的 Spring MVC 访问实例以流程图 + 源码形式简单总结:

在这里插入图片描述

  1. 客户端发起请求,到达DispatchServletdoService方法,然后进入doDispatch方法。

  2. 调用getHandler方法,在此方法中由已初始化的HandlerMapping对象根据request获取HandlerExecutionChain对象。

    mappedHandler = getHandler(processedRequest);
    
  3. 调用getHandlerAdapter方法,根据HandlerExecutionChain对象中的处理器 handler 来获取 HandlerAdapter

    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    

    执行HandlerAdapter对象的handle方法,获取ModelAndView对象

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
  4. mv返回给DispatchServlet,以便其继续利用mv来进行后置处理和渲染。

  5. 利用mv来进行视图解析

    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    render(mv, request, response);
    
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    
    View view = viewResolver.resolveViewName(viewName, locale);
    
  6. 将模型渲染到前端视图

    view.render(mv.getModelInternal(), request, response);
    
发布了79 篇原创文章 · 获赞 322 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_15329947/article/details/104031586