Spring MVC execution process

Posted by pdunn on Sun, 13 Feb 2022 05:15:30 +0100

1. Spring MVC common components

  • Dispatcher servlet: front-end controller, which does not need to be developed by engineers and is provided by the framework

Function: handle the request and response in a unified way. It is the center of the whole process control, which calls other components to handle the user's request

  • Handler mapping: processor mapper, which does not need to be developed by engineers and is provided by the framework

Function: find the Handler according to the requested url, method and other information, that is, the controller method

  • Handler: processor, which needs to be developed by engineers

Function: under the control of dispatcher servlet, the Handler handles specific user requests

  • HandlerAdapter: processor adapter. It does not need to be developed by engineers. It is provided by the framework

Function: execute the processor (controller method) through the HandlerAdapter

  • ViewResolver: View resolver, which does not need to be developed by engineers and is provided by the framework

Function: analyze the view and get the corresponding view, such as ThymeleafView, InternalResourceView and RedirectView

  • View: View

Function: display the model data to users through the page

2. Dispatcher servlet initialization process

Dispatcher Servlet is essentially a Servlet, so it naturally follows the life cycle of the Servlet. Therefore, it is the Servlet life cycle to schedule.

 

a> Initialize WebApplicationContext

Category: org springframework. web. servlet. FrameworkServlet

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        // Create WebApplicationContext
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            // Refresh WebApplicationContext
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        // Share IOC container in application domain
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

b> Create WebApplicationContext

Category: org springframework. web. servlet. FrameworkServlet

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }
    // Create IOC container objects through reflection
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // Set parent container
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

c> Dispatcher servlet initialization policy

After the FrameworkServlet creates the WebApplicationContext, refresh the container and call onRefresh(wac). This method is rewritten in the dispatcher servlet and calls the initStrategies(context) method to initialize the policy, that is, initialize each component of the dispatcher servlet

Category: org springframework. web. servlet. DispatcherServlet

protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

3. DispatcherServlet calls the component to process the request

a>processRequest()

FrameworkServlet rewrites service() and doXxx() in HttpServlet, which invoke processRequest(request, response).

Category: org springframework. web. servlet. FrameworkServlet

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

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
		// To execute a service, doService() is an abstract method, which is rewritten in the dispatcher servlet
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

b>doService()

Category: org springframework. web. servlet. DispatcherServlet

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

    // Make framework objects available to handlers and view objects.
    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);
    }

    RequestPath requestPath = null;
    if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
        requestPath = ServletRequestPathUtils.parseAndCache(request);
    }

    try {
        // Processing requests and responses
        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);
            }
        }
        if (requestPath != null) {
            ServletRequestPathUtils.clearParsedRequestPath(request);
        }
    }
}

c>doDispatch()

Category: org springframework. web. servlet. DispatcherServlet

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 {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            /*
            	mappedHandler: Call chain
                Including handler, interceptorList and interceptorIndex
            	handler: The controller method that matches the request sent by the browser
            	interceptorList: A collection of all interceptors that handle controller methods
            	interceptorIndex: Interceptor index, which controls the execution of interceptor afterCompletion()
            */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
           	// Create the corresponding processor adapter through the controller method and call the corresponding controller method
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            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;
                }
            }
			
            // Call the interceptor's preHandle()
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // The processor adapter calls the specific controller method and finally obtains the ModelAndView object
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            // Call postHandle() of interceptor
            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);
        }
        // Subsequent processing: processing model data and rendering views
        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);
            }
        }
    }
}

d>processDispatchResult()

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        // Processing model data and rendering views
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        // Call the interceptor's afterCompletion()
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

4. Execution process of spring MVC

1) The user sends a request to the server, which is captured by the DispatcherServlet of the spring MVC front-end controller.

2) DispatcherServlet parses the request URL, obtains the request resource identifier (URI), and judges the mapping corresponding to the request URI:

a) Does not exist

i. Then judge whether MVC: default servlet handler is configured

ii. If it is not configured, the console reports that the mapping cannot be found, and the client displays 404 error

iii. if it is configured, access the target resources (generally static resources, such as JS, CSS and HTML). If the client is not found, 404 error will be displayed

 

b) If it exists, execute the following process

3) According to the URI, call HandlerMapping to obtain all relevant objects configured by the Handler (including the Handler object and the interceptor corresponding to the Handler object), and finally return in the form of HandlerExecutionChain object.

4) DispatcherServlet selects an appropriate HandlerAdapter according to the obtained Handler.

5) If the HandlerAdapter is successfully obtained, the pre handler (...) method [forward] of the interceptor will be executed

6) Extract the model data in the Request, fill in the Handler input parameters, and start executing the Handler (Controller) method to process the Request. In the process of filling in the parameters of the Handler, Spring will help you do some extra work according to your configuration:

a) Httpmessageconverter: converts the request message (such as Json, xml and other data) into an object, and converts the object into the specified response information

b) Data conversion: perform data conversion on the request message. Such as converting String to Integer, Double, etc

c) Data format: format the request message. Such as converting a string into a formatted number or a formatted date

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

7) After the handler is executed, a ModelAndView object is returned to the dispatcher servlet.

8) The execution of the interceptor's postHandle(...) will begin Method [reverse].

9) According to the returned ModelAndView (whether there is an exception will be judged at this time: if there is an exception, execute the HandlerExceptionResolver for exception handling), select a suitable ViewResolver for View resolution, and render the View according to the Model and View.

10) After rendering the view, execute the after completion (...) method of the interceptor [reverse].

11) Returns the rendering result to the client.

Topics: Spring MVC