A lot of people use SpringMVC, but they don’t know how it handles requests. When we learn something, understanding it makes it easier to use it. Let’s take a look at how SpringMVC handles requests.

The manner in which the request flow is made

First above:

Spring MVC framework is also a Web framework based on request driver, and use the front controller pattern (are used to provide a centralized request processing mechanism, all requests will be handled by a single processor to design, then distributed to the corresponding page request according to the mapping rule controller (action/processor) for processing. Let’s take a look at the Spring MVC request process as a whole:

  1. First, the user sends a request, which is captured by the SpringMVC front-end controller (DispatherServlet).

  2. The front-end controller (DispatherServlet) parses the REQUEST URL to obtain the request URI, and invokes HandlerMapping according to the URI.

  3. The front-end controller (DispatherServlet) retrieves the returned HandlerExecutionChain (including the Handler object and its corresponding interceptor);

  4. The DispatcherServlet selects an appropriate HandlerAdapter based on the HandlerExecutionChain obtained. (Note: If the HandlerAdapter is successfully obtained, execution of the interceptor’s preHandler(…) begins at this point.) Methods);

  5. The HandlerAdapter ADAPTS and executes the Handler based on the requested Handler; The HandlerAdapter extracts the model data from the Request, populates the Handler entry parameter, and starts executing Handler (Controller). Depending on the configuration, Spring does some extra work during the entry of the populated Handler:

    HttpMessageConveter: Converts the request message (such as Json and XML data) into an object, which is converted into the specified response information.

    Data transformation: Data transformation of the request message. Such as String to Integer, Double, and so on;

    Data formatting: converting a string to a formatted number or date, etc.

    Data validation: verifies the validity of the data (length, format, etc.) and stores the validation results in BindingResult or Error);

  6. When this Handler completes, it returns a ModelAndView to the HandlerAdaptor.

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

  8. After receiving the ModelAndView, the front-end controller requests the corresponding view parser.

  9. The View parser parses the ModelAndView and returns the corresponding View.

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

  11. Finally, the front-end controller sends the rendered page response to the user or client.

A case in field

SpringMVC requests to perform source code interpretation

For the SpringMVC project all request entries (except static resources) start here with the front-end controller DispatcherServlet configured in the web.xml file:

<! -- Servlet request dispatcher -->
<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>
  <! Initialize the Servlet when the container is started
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <! Intercept all requests, intercept all.do requests.
  <url-pattern>/</url-pattern>
</servlet-mapping>
Copy the code

The UML inheritance diagram of DispatcherServlet is as follows:

Here is the inheritance structure of the blue line: DispatcherServlet–>FrameworkServlet–>HttpServletBean–>HttpServlet–>GenericServlet–>Servlet –>

For handling web requests, we all know that we override the service method by inheriting HttpServlet. If we open the DispatcherServlet source code, we do not see the service method we are looking for. You can see that the superclass overrides 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) {
      processRequest(request, response);
   }
   else {
      super.service(request, response); }}Copy the code

From the source code analysis method when the request for the patch request or to null processRequest0 execution method, other what it calls the superclass method of service, we all know, the SpringMVC request most is get | post request is given priority to, HttpServletBean (HttpServletBean)–> HttpServletBean (HttpServletBean);

HttpServlet#service

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;
        
        if(! (reqinstanceof 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); }}Copy the code

The HttpServlet service is overloaded and calls different methods depending on the request type. For example, if the request method is GET request, the HttpServlet service is overloaded and calls doGet request. Httpservlets have doGet implementations, but there are also doGet implementations in inherited subclasses. Which method is called? It’s obvious to call a subclass’s doGet method (object-oriented polymorphism!!). FrameworkServlet is the outermost class that implements the doGet method:

FrameworkServlet#doGet&processRequest

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

   processRequest(request, response);
}

	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

        // System timing start time
		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
        / / the internationalization
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext = buildLocaleContext(request);
        // Build the ServletRequestAttributes object
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
		// Asynchronous management
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
		// Initialize 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 {
             RequestAttributes to LocaleContext and ServiceRequestAttributes to LocaleContextHolder and RequestContextHolder to avoid affecting processing outside of the Servlet, such as Filter
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if(requestAttributes ! =null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
            / / release ServletRequestHandlerEvent news, whether the request execution success will release informationpublishRequestHandledEvent(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); }}Copy the code

The method does a few things: Internationalize the setup, create a ServletRequestAttributes object, and initialize the context holders. If you want to obtain request or response objects from a method, you can call LocaleContextHolder and then call doService. For doService methods, there is no implementation provided by the FrameworkServlet class, which is implemented by a DispatcherServlet subclass:

DispatcherServlet#doService

The entry method for the DispatcherServlet is doService. Since this class inherits from the FrameworkServlet class, it overrides the doService() method:

@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 = newHashMap<>(); 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)); }}}/ / the Spring context
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    // Internationalize the parser
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    // Topic resolver
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    / / theme
   request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    // Redirect data
   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 sets the relevant properties for actual request processing
      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); }}}}Copy the code

The entire method looks like this: Handle requests for include tags, put the context in the request properties, put the internationalization parser in the Request properties, put the topic parser in the Request properties, put the topic in the Request properties, Process the redirected request data and finally call the core doDispatch method to process the request:

DispatcherServlet#doDispatch

This method is called in the doService method and designs the entire request processing flow from the bottom up:

  • Find Handler according to request
  • Find the corresponding HandlerAdapter based on the Handler
  • Use a HandlerAdapter to handle handlers
  • Call the processDispatchResult method to process the results from above (including the 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 {
          // Verify whether it is an upload request. Yes Upload request parsing is performed. Otherwise, request is returnedprocessedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request);HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain The HandlerInterceptor object contains a list of handlers and interceptors
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Get the corresponding HandlerAdaptor object based on the resulting Handler
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Handle last-modified GET, HEAD requests
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            // If the data has not changed, return the last data directly to improve efficiency
             if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return; }}// Execute the Interceptor's preHandle
         if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
         }

         // Execute Handler to return ModelAndView
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

          // If asynchronous processing is required, return directly
         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         // When the view is empty, set the default view according to the request. For example, Handler returns void
         applyDefaultViewName(processedRequest, mv);
         // Execute the corresponding Interceptor's 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);
      }
       // Process the returned results, including handling exceptions, rendering the page, and issuing afterCompletion notifications that trigger the Interceptor
      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); }}}}Copy the code
  1. DoDispatcher first checks whether the upload request, if it is then converts the request to MultipartHttpServletRequest, and multipartRequestParsed flag is set to true;

  2. GetHandler Handler chain by getHandler;

  3. Process last-Modified of GET and HEAD requests. Here, it mainly determines whether last-Modified value is Modified to process and decide whether to use cached data.

  4. The corresponding Interceptor’s preHandle is called in turn to intercept the Interceptor.

  5. After the interceptor preHandle is executed, the Handler will be routed through the HandlerAdapter to the corresponding Handler (the Controller method is actually executed). After the Handler has processed the request, if asynchronous processing is required, the Handler will return the request directly. If asynchronous processing is not required, set the default view when the view is empty, and then execute the corresponding Interceptor’s postHandle.

  • Handler: Processor, which directly corresponds to C in MVC, also known as Controller layer, has many specific manifestations, including classes and methods (methods are usually the most common), because its definition is Object. All of the @requestMapping methods we annotated in the method can be treated as a Handler, anything that can actually handle the request can be treated as a Handler.
  • HandlerMapping: SpringMVC handles many requests. Each request requires a Handler. In this case, HandlerMapping is used to search for the request.
  • HandlerAdapter: Adapters. Different handlers need to find different handlerAdapters to call handlers. Just as tools are needed in a factory, handlerAdapters use handleradapters to complete tasks, and HandlerMapping is used to find tools based on what needs to be done. Need video supporting materials or other information + our little sister V Lezijie007 (add friends code -98, no remarks no add)

DispatcherServlet#processDispatchResult

The processDispatchResult method is used to process the previously returned results, including handling the exception, rendering the page, and the afterCompletion method that triggers the Interceptor. The exception handled was generated during the processing of 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 an exception is thrown during the request, handle the exception
   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); }}// Perform page rendering
   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;
   }

   // afterCompletion of the Interceptor request, afterCompletion is triggered
   if(mappedHandler ! =null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null); }}Copy the code

Render view:

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());
      }
       // Render page processing
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "]", ex);
      }
      throwex; }}Copy the code

Today we come to understand the MVC framework MVC core ideas, SpringMVC internal request process analysis and source code level interpretation, so that we can really understand the whole framework from the bottom level of the original appearance of the implementation, and finally to summarize today’s source analysis process with a graph.

Extensions to the MVC

Model-view-controller (MVC) is a well-known design idea based on designing interface applications. It decouples the business logic from the interface by separating the roles of the model, view, and controller in the application. Typically, the model is responsible for encapsulating application data for presentation at the view layer. The view simply presents the data and does not contain any business logic. The controller receives the request from the user and invokes the background service (Service or DAO) to process the business logic. After processing, the back-end business layer may return some data to be displayed in the view layer. The controller collects this data and prepares the model for presentation at the view layer. The core idea of the MVC pattern is to separate the business logic from the interface, allowing them to change individually without affecting each other.

A lot of people use SpringMVC, but they don’t know how it handles requests. When we learn something, understanding it makes it easier to use it. Let’s take a look at how SpringMVC handles requests.

The manner in which the request flow is made

First above:

Spring MVC framework is also a Web framework based on request driver, and use the front controller pattern (are used to provide a centralized request processing mechanism, all requests will be handled by a single processor to design, then distributed to the corresponding page request according to the mapping rule controller (action/processor) for processing. Let’s take a look at the Spring MVC request process as a whole:

  1. First, the user sends a request, which is captured by the SpringMVC front-end controller (DispatherServlet).

  2. The front-end controller (DispatherServlet) parses the REQUEST URL to obtain the request URI, and invokes HandlerMapping according to the URI.

  3. The front-end controller (DispatherServlet) retrieves the returned HandlerExecutionChain (including the Handler object and its corresponding interceptor);

  4. The DispatcherServlet selects an appropriate HandlerAdapter based on the HandlerExecutionChain obtained. (Note: If the HandlerAdapter is successfully obtained, execution of the interceptor’s preHandler(…) begins at this point.) Methods);

  5. The HandlerAdapter ADAPTS and executes the Handler based on the requested Handler; The HandlerAdapter extracts the model data from the Request, populates the Handler entry parameter, and starts executing Handler (Controller). Depending on the configuration, Spring does some extra work during the entry of the populated Handler:

    HttpMessageConveter: Converts the request message (such as Json and XML data) into an object, which is converted into the specified response information.

    Data transformation: Data transformation of the request message. Such as String to Integer, Double, and so on;

    Data formatting: converting a string to a formatted number or date, etc.

    Data validation: verifies the validity of the data (length, format, etc.) and stores the validation results in BindingResult or Error);

  6. When this Handler completes, it returns a ModelAndView to the HandlerAdaptor.

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

  8. After receiving the ModelAndView, the front-end controller requests the corresponding view parser.

  9. The View parser parses the ModelAndView and returns the corresponding View.

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

  11. Finally, the front-end controller sends the rendered page response to the user or client.

A case in field

SpringMVC requests to perform source code interpretation

For the SpringMVC project all request entries (except static resources) start here with the front-end controller DispatcherServlet configured in the web.xml file:

<! -- Servlet request dispatcher -->
<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>
  <! Initialize the Servlet when the container is started
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <! Intercept all requests, intercept all.do requests.
  <url-pattern>/</url-pattern>
</servlet-mapping>
Copy the code

The UML inheritance diagram of DispatcherServlet is as follows:

Here is the inheritance structure of the blue line: DispatcherServlet–>FrameworkServlet–>HttpServletBean–>HttpServlet–>GenericServlet–>Servlet –>

For handling web requests, we all know that we override the service method by inheriting HttpServlet. If we open the DispatcherServlet source code, we do not see the service method we are looking for. You can see that the superclass overrides 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) {
      processRequest(request, response);
   }
   else {
      super.service(request, response); }}Copy the code

From the source code analysis method when the request for the patch request or to null processRequest0 execution method, other what it calls the superclass method of service, we all know, the SpringMVC request most is get | post request is given priority to, HttpServletBean (HttpServletBean)–> HttpServletBean (HttpServletBean);

HttpServlet#service

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;
        
        if(! (reqinstanceof 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); }}Copy the code

The HttpServlet service is overloaded and calls different methods depending on the request type. For example, if the request method is GET request, the HttpServlet service is overloaded and calls doGet request. Httpservlets have doGet implementations, but there are also doGet implementations in inherited subclasses. Which method is called? It’s obvious to call a subclass’s doGet method (object-oriented polymorphism!!). FrameworkServlet is the outermost class that implements the doGet method:

FrameworkServlet#doGet&processRequest

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

   processRequest(request, response);
}

	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

        // System timing start time
		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
        / / the internationalization
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext = buildLocaleContext(request);
        // Build the ServletRequestAttributes object
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
		// Asynchronous management
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
		// Initialize 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 {
             RequestAttributes to LocaleContext and ServiceRequestAttributes to LocaleContextHolder and RequestContextHolder to avoid affecting processing outside of the Servlet, such as Filter
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if(requestAttributes ! =null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
            / / release ServletRequestHandlerEvent news, whether the request execution success will release informationpublishRequestHandledEvent(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); }}Copy the code

The method does a few things: Internationalize the setup, create a ServletRequestAttributes object, and initialize the context holders. If you want to obtain request or response objects from a method, you can call LocaleContextHolder and then call doService. For doService methods, there is no implementation provided by the FrameworkServlet class, which is implemented by a DispatcherServlet subclass:

DispatcherServlet#doService

The entry method for the DispatcherServlet is doService. Since this class inherits from the FrameworkServlet class, it overrides the doService() method:

@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 = newHashMap<>(); 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)); }}}/ / the Spring context
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    // Internationalize the parser
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    // Topic resolver
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    / / theme
   request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    // Redirect data
   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 sets the relevant properties for actual request processing
      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); }}}}Copy the code

The entire method looks like this: Handle requests for include tags, put the context in the request properties, put the internationalization parser in the Request properties, put the topic parser in the Request properties, put the topic in the Request properties, Process the redirected request data and finally call the core doDispatch method to process the request:

DispatcherServlet#doDispatch

This method is called in the doService method and designs the entire request processing flow from the bottom up:

  • Find Handler according to request
  • Find the corresponding HandlerAdapter based on the Handler
  • Use a HandlerAdapter to handle handlers
  • Call the processDispatchResult method to process the results from above (including the 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 {
          // Verify whether it is an upload request. Yes Upload request parsing is performed. Otherwise, request is returnedprocessedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request);HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain The HandlerInterceptor object contains a list of handlers and interceptors
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Get the corresponding HandlerAdaptor object based on the resulting Handler
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Handle last-modified GET, HEAD requests
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            // If the data has not changed, return the last data directly to improve efficiency
             if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return; }}// Execute the Interceptor's preHandle
         if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
         }

         // Execute Handler to return ModelAndView
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

          // If asynchronous processing is required, return directly
         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         // When the view is empty, set the default view according to the request. For example, Handler returns void
         applyDefaultViewName(processedRequest, mv);
         // Execute the corresponding Interceptor's 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);
      }
       // Process the returned results, including handling exceptions, rendering the page, and issuing afterCompletion notifications that trigger the Interceptor
      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); }}}}Copy the code
  1. DoDispatcher first checks whether the upload request, if it is then converts the request to MultipartHttpServletRequest, and multipartRequestParsed flag is set to true;

  2. GetHandler Handler chain by getHandler;

  3. Process last-Modified of GET and HEAD requests. Here, it mainly determines whether last-Modified value is Modified to process and decide whether to use cached data.

  4. The corresponding Interceptor’s preHandle is called in turn to intercept the Interceptor.

  5. After the interceptor preHandle is executed, the Handler will be routed through the HandlerAdapter to the corresponding Handler (the Controller method is actually executed). After the Handler has processed the request, if asynchronous processing is required, the Handler will return the request directly. If asynchronous processing is not required, set the default view when the view is empty, and then execute the corresponding Interceptor’s postHandle.

  • Handler: Processor, which directly corresponds to C in MVC, also known as Controller layer, has many specific manifestations, including classes and methods (methods are usually the most common), because its definition is Object. All of the @requestMapping methods we annotated in the method can be treated as a Handler, anything that can actually handle the request can be treated as a Handler.
  • HandlerMapping: SpringMVC handles many requests. Each request requires a Handler. In this case, HandlerMapping is used to search for the request.
  • HandlerAdapter: Adapters. Different handlers need to find different handlerAdapters to call handlers. Just as tools are needed in a factory, handlerAdapters use handleradapters to complete tasks, and HandlerMapping is used to find tools based on what needs to be done. Need video supporting materials or other information + our little sister V Lezijie007 (add friends code -98, no remarks no add)

DispatcherServlet#processDispatchResult

The processDispatchResult method is used to process the previously returned results, including handling the exception, rendering the page, and the afterCompletion method that triggers the Interceptor. The exception handled was generated during the processing of 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 an exception is thrown during the request, handle the exception
   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); }}// Perform page rendering
   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;
   }

   // afterCompletion of the Interceptor request, afterCompletion is triggered
   if(mappedHandler ! =null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null); }}Copy the code

Render view:

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());
      }
       // Render page processing
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "]", ex);
      }
      throwex; }}Copy the code

Today we come to understand the MVC framework MVC core ideas, SpringMVC internal request process analysis and source code level interpretation, so that we can really understand the whole framework from the bottom level of the original appearance of the implementation, and finally to summarize today’s source analysis process with a graph.

Extensions to the MVC

Model-view-controller (MVC) is a well-known design idea based on designing interface applications. It decouples the business logic from the interface by separating the roles of the model, view, and controller in the application. Typically, the model is responsible for encapsulating application data for presentation at the view layer. The view simply presents the data and does not contain any business logic. The controller receives the request from the user and invokes the background service (Service or DAO) to process the business logic. After processing, the back-end business layer may return some data to be displayed in the view layer. The controller collects this data and prepares the model for presentation at the view layer. The core idea of the MVC pattern is to separate the business logic from the interface, allowing them to change individually without affecting each other.