Teach you how SpringMVC handles requests

Many people will use SpringMVC, but the way it handles requests is not clear. When we learn a knowledge, understanding it will make us better use it. Let's take a look at how SpringMVC handles requests.

Method of request process

First picture:

Teach you how SpringMVC handles requests

The Spring MVC framework is also a request-driven Web framework , and uses the front controller pattern (is used to provide a centralized request processing mechanism, all requests will be processed by a single handler for design, and then mapped according to the request The rules are distributed to the corresponding page controllers (actions/processors) for processing. First, let us look at the process of Spring MVC processing requests as a whole:

  1. First, the user sends a request, and the request is captured by the SpringMVC front controller (DispatherServlet);

  2. The front controller (DispatherServlet) parses the request URL to obtain the request URI, and calls HandlerMapping according to the URI;

  3. The front controller (DispatherServlet) gets the returned HandlerExecutionChain (including the Handler object and the interceptor corresponding to the Handler object);

  4. DispatcherServlet selects an appropriate HandlerAdapter according to the HandlerExecutionChain obtained. (Note: If the HandlerAdapter is successfully obtained, the preHandler(...) method of the interceptor will be executed at this time);

  5. HandlerAdapter adapts and executes the corresponding Handler according to the requested Handler; HandlerAdapter extracts the model data in the Request, fills in the Handler input parameters, and starts to execute the Handler (Controller). In the process of filling Handler input parameters, according to the configuration, Spring will do some extra work:

    HttpMessageConveter: Convert the request message (such as Json, xml, etc.) into an object, and convert the object into the specified response information;

    Data conversion: Data conversion of the request message. Such as String conversion into Integer, Double, etc.;

    Data formatting: such as converting character strings into formatted numbers or formatted dates;

    Data verification: verify the validity of the data (length, format, etc.), and store the verification result in BindingResult or Error);

  6. After Handler is executed, return a ModelAndView (ie model and view) to HandlerAdaptor;

  7. The HandlerAdaptor adapter returns the execution result ModelAndView to the front controller;

  8. After receiving ModelAndView, the front controller requests the corresponding view resolver;

  9. The view resolver parses ModelAndView and returns the corresponding View;

  10. Render the view and return the rendered view to the front controller;

  11. Finally, the front controller responds to the user or client with the rendered page.

Case practice

SpringMVC request execution source code interpretation

For all request entries (except static resources) of the SpringMVC project, here are the front controller DispatcherServlet configured in the web.xml file:

<!-- servlet请求分发器 -->
<servlet>
  <servlet-name>springMvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:servlet-context.xml</param-value>
  </init-param>
  <!-- 表示启动容器时初始化该Servlet -->
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <!-- 这是拦截请求, /代表拦截所有请求,拦截所有.do请求 -->
  <url-pattern>/</url-pattern>
</servlet-mapping>

The DispatcherServlet UML inheritance diagram is as follows:

Teach you how SpringMVC handles requests

Here we focus on the blue line part of the inheritance structure: DispatcherServlet-->FrameworkServlet-->HttpServletBean-->HttpServlet-->GenericServlet-->Servlet. The core sequence diagram for the request is as follows:

Teach you how SpringMVC handles requests

For the processing of web requests, everyone knows that the service method is rewritten by inheriting HttpServlet. Open the DispatcherServlet source code and find that the service method we are looking for is not seen here. At this time, look for the parent class FrameworkServlet as follows: You can see the parent class Override the HttpServlet service method.

FrameworkServlet#service

/**
 * Override the parent class implementation in order to intercept PATCH requests.
 */
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
   HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
   if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
      proce***equest(request, response);
   }
   else {
      super.service(request, response);
   }
}

From the source code analysis, the proceed***equest0 method is executed when the request method is a patch request or null. In other cases, the parent service method is called. Everyone knows that most of the SpringMVC requests are mainly get|post requests. Continue at this time Look up the FrameworkServlet parent class HttpServletBean (the abstract class inherits HttpServlet and does not rewrite the service method, so continue to look up) --> HttpServlet service method:

HttpServlet#service

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;

        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }

        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }
}

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

It can be seen that the HttpServlet service has been overloaded, and different processing methods are called according to different request types. Here, take the get request as an example. When the request method is a get request, the doGet method is called in the overloaded service method for processing. Special Note that: HttpServlet has a doGet method implementation, but there is also a doGet method implementation in inherited subclasses. Which method is called? Obviously calling the subclass's doGet method (object-oriented polymorphism!!!) From the inherited UML diagram, the outermost subclass implements the doGet method for FrameworkServlet:

FrameworkServlet#doGet&proce***equest

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   proce***equest(request, response);
}

    protected final void proce***equest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 系统计时开始时间
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        // 国际化
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);
        //构建ServletRequestAttributes对象
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        //异步管理
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //初始化ContextHolders
        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response);
        }
        catch (ServletException | IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
             //恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            logResult(request, response, failureCause, asyncManager);
            //发布ServletRequestHandlerEvent消息,这个请求是否执行成功都会发布消息的
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

// initContextHolders(request, localeContext, requestAttributes);
    private void initContextHolders(HttpServletRequest request,
            @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
    }

This method probably does these few things: international settings, create ServletRequestAttributes object, initialize context holders (that is, put the Request object into the thread context, if you want to get the request and response objects in the method later, you can call LocaleContextHolder can correspond to the method), and then call the doService method. For the doService method, the FrameworkServlet class does not provide an implementation, this method is implemented by the DispatcherServlet subclass:

DispatcherServlet#doService

The entry method for executing processing in DispatcherServlet is doService. Since this class inherits from the FrameworkServlet class, the doService() method is overridden:

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   logRequest(request);

   // Keep a snapshot of the request attributes in case of an include,
   // to be able to restore the original attributes after the include.
   Map<String, Object> attributesSnapshot = null;
   if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
         String attrName = (String) attrNames.nextElement();
         if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
            attributesSnapshot.put(attrName, request.getAttribute(attrName));
         }
      }
   }

    //Spring上下文
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    //国际化解析器
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    //主题解析器
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    //主题
   request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    //重定向的数据  
   if (this.flashMapManager != null) {
      FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
      if (inputFlashMap != null) {
         request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
      }
      request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
      request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
   }

   try {
      //request设置完相关的属性做真正的请求处理
      doDispatch(request, response);
   }
   finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
         // Restore the original attribute snapshot, in case of an include.
         if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
         }
      }
   }
}

The entire method looks at the processing operations: processing the request of the include tag, placing the context in the request attribute, placing the internationalization parser in the request attribute, placing the theme parser in the request attribute, and placing the theme in the request attribute In the request attribute, the core method of doDispatch is called to process the request after processing the redirected request data:

DispatcherServlet#doDispatch

This method is called in the doService method, and the entire request processing flow is designed from the bottom:

  • Find Handler according to request
  • Find the corresponding HandlerAdapter according to Handler
  • Use HandlerAdapter to handle Handler
  • Call the processDispatchResult method to process the above results (including View rendering and output to the user)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
          // 校验是否为上传请求 是上传请求执行解析 否则返回request
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // 根据访问的Handler 返回指定对应的HandlerExecutionChain对象 这里从HandlerMapping 集合中查找 HandlerExecutionChain 对象包含Handler与拦截器HandlerInterceptor列表
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // 根据得到的Handler 获取对应的HandlerAdaptor对象
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // 处理GET、HEAD请求的Last-Modified
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            //当数据没有更改时,就直接返回上次的数据,提高效率
             if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         //执行Interceptor的preHandle 
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // 执行Handler 返回ModelAndView
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

          //如果需要异步处理,直接返回
         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         //当view为空时,根据request设置默认view,如Handler返回值为void
         applyDefaultViewName(processedRequest, mv);
         //执行相应Interceptor的postHandle 
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
       //处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletion
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}
  1. doDispatcher first checks whether it is an upload request, and if so, converts the request to MultipartHttpServletRequest and sets the multipartRequestParsed flag to true;

  2. Get HandlerExecutionChain through getHandler;
  3. Processing Last-Modified of GET and HEAD requests, here is mainly to determine whether the Last-Modified value has been modified to determine whether to use cached data;
  4. Next, call the preHandle of the corresponding Interceptor in turn to execute the interceptor interception operation;
  5. After the interceptor preHandle method is executed, the corresponding Handler execution is adapted through the HandlerAdapter (here is the Controller method to be executed). After the Handler has processed the request, if asynchronous processing is required, it will return directly. If asynchronous processing is not required, When the view is empty, set the default view, and then execute the postHandle of the corresponding Interceptor.
  • Handler: The processor directly corresponds to the C in MVC, which is the Controller layer. It has many specific manifestations, which can be classes or methods (usually methods), because its definition is Object. All methods of @RequestMapping marked in the method can be regarded as a Handler, as long as the request can be actually processed can be regarded as a Handler.
  • HandlerMapping: Used to find Handler. Many requests are processed in SpringMVC. Each request needs a Handler to process. After receiving the request, which Handler is required to process, the search is realized through HandlerMapping.
  • HandlerAdapter: adapter, different Handlers need to find different HandlerAdapter to call Handler. Just as tools are needed in the factory, workers (HandlerAdapter) use tools (Handler) to complete the work, and HandlerMapping is used to find the corresponding tools according to the work that needs to be completed.

DispatcherServlet#processDispatchResult

The processDispatchResult method is mainly used to process the previously returned results, including three parts: handling exceptions, rendering the page, and triggering the afterCompletion method of the Interceptor. The processed exceptions are generated during the process of processing the request doDispatch method.

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);
      }
   }

   //执行页面渲染操作
   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;
   }

   // Handler请求处理完,触发Interceptor的afterCompletion
   if (mappedHandler != null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}

Render view rendering:

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() + "'");
      }
   }

   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;
   }
}

Today we came to understand the core ideas of MVC in the SpringMVC framework, SpringMVC internal request process analysis and source-level code interpretation, so that everyone can truly understand the original execution of the entire framework from the bottom level, and finally a picture to summarize today's source code analysis execution process .

Teach you how SpringMVC handles requests

Extension~MVC

Model-View-Controller (MVC) is a well-known design concept based on designing interface applications. It decouples business logic from the interface mainly by separating the roles of model, view, and controller in the application. Usually, the model is responsible for encapsulating the application data and displaying it in the view layer. The view only displays the data and does not contain any business logic. The controller is responsible for receiving requests from users and calling background services (service or dao) to process business logic. After processing, the background business layer may return some data for display in the view layer. The controller collects these data and prepares the model for display in the view layer. The core idea of ​​the MVC pattern is to separate the business logic from the interface and allow them to be changed separately without affecting each other.

Teach you how SpringMVC handles requests

Guess you like

Origin blog.51cto.com/14819669/2541655