How exactly does SpringMVC handle requests?

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> <! <load-on-startup>1</load-on-startup> </ Servlet > <servlet-mapping> <servlet-name>springMvc</servlet-name> <! <url-pattern>/</url-pattern> </servlet-mapping>Copy the code

The UML inheritance diagram of DispatcherServlet is as follows:

The blue line inheritance structure is 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 () — > HttpServlet service ();

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);
        }
    }
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 startTime long startTime = system.currenttimemillis (); Throwable failureCause = null; . / / the internationalization LocaleContext previousLocaleContext = LocaleContextHolder getLocaleContext (); LocaleContext localeContext = buildLocaleContext(request); / / build 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 {// Restore the original LocaleContext and ServiceRequestAttributes to LocaleContextHolder and RequestContextHolder. For example, Filter resetContextHolders(Request, previousLocaleContext, previousAttributes); if (requestAttributes ! = null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); / / release ServletRequestHandlerEvent message, If the request execution success will release 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); }}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 = 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 context request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); Request. setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeresolver); 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 {// 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;

    // Check whether it is an upload request. Yes Upload request parsing is performed. Otherwise, request processedRequest = checkMultipart(request) is returned. multipartRequestParsed = (processedRequest ! = request); HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain HandlerInterceptor mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; HandlerAdapter ha = getHandlerAdapter(mappedHandler.gethandler ()); // Last-modified 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. If (new ServletWebRequest(Request, response).checknotModified (lastModified) &&isget) {return; }} return Interceptor's preHandle if (! mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Execute Handler to return ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.gethandler ()); / / for asynchronous processing, direct return the if (asyncManager. IsConcurrentHandlingStarted () {return; } // When the view is empty, set the default view according to the request. For example, the Handler returns void applyDefaultViewName(processedRequest, mv); . / / the implementation of corresponding 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); } // Handle the returned results, including handling exceptions, rendering pages, AfterCompletion processDispatchResult triggers the Interceptor (processedRequest, Response, mappedHandler, mv, dispatchException);Copy the code

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

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



The view + “] “, the ex);

}

throw ex;

}

}

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. MVC model-View-Controller (MVC) is a well-known design concept based on the design of 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. imG-2J1SGTJI-1607396371047Copy the code