Spring MVC source code analysis

Posted by Sephirangel on Mon, 03 Jan 2022 08:58:07 +0100

I. working principle of spring Web

1.1 Tomcat & Spring MVC configuration

Spring MVC is a very important and commonly used framework in Java backend. This article will analyze the source code of spring MVC. web.xml configuration. Although we do not need to write servlets to use spring MVC, spring MVC encapsulates servlets and provides dispatcherservlets to help us deal with them. So it needs to be on the web XML to configure DispatcherServlet. It can be seen that the mapped url of dispatcher Servlet is /, so all requests will be intercepted and processed to us. Let's go in and have a look.

 

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--Configure front-end controller-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- load SpringMVC configuration file -->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- Load this at startup Servlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    //Omit other configurations
</web-app>

An online architecture diagram of Tomcat and spring MVC integration is shown in the figure below. On the left is the class structure diagram of Tomcat and on the right is the class structure diagram of spring MVC. As we know, when Tomcat is started, a dispatcher servlet class will be instantiated and put into the Tomcat container The request will be passed directly from Tomcat to this class Then it is handed over to the business code for processing, and finally returned through view parsing through this class

1.2 Tomcat & Spring MVC architecture diagram

 

1.3 roles and responsibilities of three Servlet classes

1.3.1 HttpServletBean

Mainly do some initialization work to parse the web The parameters configured in XML are to the Servlet, such as the parameters configured in init param. Provide initServletBean() template method to implement the subclass FrameworkServlet.

1.3.2 FrameworkServlet

Associate the Servlet with the SpringIoC container. It mainly initializes the WebApplicationContext, which represents the context of spring MVC. It has a parent context, web The ContextLoaderListener listener configured in the XML configuration file initializes the container context.
The onRefresh() template method is provided to implement the subclass DispatcherServlet as the initialization entry method.

1.3.3 DispatcherServlet

The last subclass, as the front-end controller, initializes various components, such as request mapping, view parsing, exception handling, request handling, etc.

II. Initialization

We know that when a Servlet is initialized, the init() method of the Servlet will be called. If we enter the dispatcher Servlet and find that there is no such method, it must be on its integrated parent class. Dispatcher Servlet inherits from FrameworkServlet, but it is still not found. Continue to find its parent HttpServletBean.

2.1 HttpServletBean

Finally, let's find the method of init bean () and HttpServlet ().

public final void init() throws ServletException {
    //Get configuration web Parameters in XML
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }
            throw ex;
        }
    }
    //Key: an empty method, template pattern, subclass FrameworkServlet, overridden it
    initServletBean();
}

2.2 FrameworkServlet

The initServletBean() method is an initialization. The method mainly calls initWebApplicationContext() to initialize WebApplicationContext and initFrameworkServlet(). This is an empty method that can be provided to future subclasses for replication and initialization. It has not been replicated yet.

protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();

    try {
        //Important: initialize WebApplicationContext
        this.webApplicationContext = initWebApplicationContext();
        //An empty method can be provided for future subclass replication to do some initialization. It has not been replicated for the time being
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
    //Omit irrelevant code
}

Next, analyze the initwebapplicationcontext () method. initWebApplicationContext() method, which initializes WebApplicationContext and is integrated into ApplicationContext, so it is also an IoC container. Therefore, the responsibility of the FrameworkServlet class is to associate Spring with the Servlet. In addition to initializing the WebApplicationContext, this method also calls an onRefresh() method, which is also a template mode and an empty method, so that the subclass replication can be processed logically. For example, the subclass DispatcherServlet rewrites it

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    //If there is a parameter construction method and the webApplicationContext object is passed in, the judgment will be entered
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            //It has not been initialized, and the container's refresh() has not been called
            if (!cwac.isActive()) {
                //Set parent container
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        //Get the ServletContext. Previously, it was set to the ServletContext through setAttribute, but now it is obtained through getAttribute
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        //Create WebApplicationContext, set environment, parent container and local resource file
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            //Refresh is also a template mode and an empty method. It allows subclass rewriting for logical processing, and subclass DispatcherServlet rewrites it
            onRefresh(wac);
        }
    }
    //Set the container into ServletContext with setAttribute()
    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}
//WebApplicationContext
public interface WebApplicationContext extends ApplicationContext {
    //...
}

Next, let's look at the onRefresh() method replicated by the subclass DispatcherServlet.

2.3 DispatcherServlet

The responsibility of the FrameworkServlet class is to associate Spring with the servlet. For DispatcherServlet, its initialization method is onRefresh(). onRefresh() method, call initStrategies() method to initialize various components. Let's focus on the process behind initHandlerMappings()!

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {  //Parse request
    initMultipartResolver(context);  //internationalization
    initLocaleResolver(context);  //theme
    initThemeResolver(context);  //Method of handling Controller and url mapping relationship
    initHandlerMappings(context); //Initialization of the adapter, adaptation processing of the Controller in various ways, and the final return of the implementation is ModelAndView
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context); //Initialize exception handler 
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);//Initialize the view parser, convert the view information saved by ModelAndView into a view and output data
    //Initialize mapping processor
    initFlashMapManager(context);
}

III. component initialization process

Let's start the component initialization process analysis.

3.1 HandlerMapping processor mapping

Initialize the Url mapping relationship of the Controller.

Mainly map < string, handlermapping > matchingbeans = beanfactoryutils beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);, This code obtains all handlermapping from the container.

private List<HandlerMapping> handlerMappings;
//A switch that identifies whether to obtain all processor mappings. If false, search for the Bean instance named handlerMapping
private boolean detectAllHandlerMappings = true;
//The name of the specified Bean
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";

private void initHandlerMappings(ApplicationContext context) {
    //Empty collection
    this.handlerMappings = null;
    
    //A switch that defaults to true and is set to false before else logic is used
    if (this.detectAllHandlerMappings) {
        //Important: find all HandlerMapping in the container
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        //Found, sort and ensure the order
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        //Specifies to search for a handlermapping instance named handlermapping
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }
    
    //The mapping relationship cannot be found. Set a default mapping relationship
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }

    //Profile name
    private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

    //Get the configured component from the configuration file. If other components cannot be found, this method is also called for default configuration
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        //...
    }
}

If no mapping relationship is found, the default configuration will be obtained from the configuration file through the getDefaultStrategies method. If other components cannot be found, this method is also called for default configuration. Configuration file name: dispatcherservlet properties. Two default mapping relationship classes BeanNameUrlHandlerMapping and RequestMappingHandlerMapping will be added.

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

3.2 HandlerAdapter processor adapter

The initialization logic of HandlerAdapter is basically the same as HandlerMapping above. Search the container for all instances of HandlerAdapter. If not found, get the default HandlerAdapter from the configuration file.

private List<HandlerAdapter> handlerAdapters;
//Like the HandlerMapping above, a switch is used to determine whether to search all handleradapters in the container. If false, the specified Bean named handlerAdapter will be searched
private boolean detectAllHandlerAdapters = true;
//Specified HandlerAdapter instance
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";

private void initHandlerAdapters(ApplicationContext context) {
    //Empty collection
    this.handlerAdapters = null;

    //It is also a switch. By default, it is true to search all handleradapters in the container
    if (this.detectAllHandlerAdapters) {
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        //Once found, sort to ensure that the HandlerAdapter is in order
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        //Specifies to find a HandlerAdapter named HandlerAdapter
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }

    //No HandlerAdapter found. Get the default HandlerAdapter from the configuration file
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

3.3 HandlerExceptionResolver

Like the above, the exception handler searches for all instances of exception handlers from the container, and there is also a switch to search for exception handlers with specified names.

private List<HandlerExceptionResolver> handlerExceptionResolvers;
//Switch. If it is set to false, it will find the following Bean instance named handlerExceptionResolver
private boolean detectAllHandlerExceptionResolvers = true;
//Specify an instance named handlerExceptionResolver
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";

private void initHandlerExceptionResolvers(ApplicationContext context) {
    //Empty collection
    this.handlerExceptionResolvers = null;

    //Switch, default true
    if (this.detectAllHandlerExceptionResolvers) {
        //Search all exception handlers
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        //Found it
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
            //sort
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
        try {
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

    //There is no exception handler. Get the default exception handler from the configuration file
    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

3.4 ViewResolver view resolver

Like the above parser logic, the view parser has a switch to decide whether to search all the in the container or those with a specified name.

private List<ViewResolver> viewResolvers;
//switch
private boolean detectAllViewResolvers = true;
//Specify name
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";

private void initViewResolvers(ApplicationContext context) {
    //Empty collection
    this.viewResolvers = null;

    if (this.detectAllViewResolvers) {
        //Search all view parsers
        Map<String, ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers = new ArrayList<>(matchingBeans.values());
            //sort
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else {
        try {
            //Searches for a view parser Bean named viewResolver
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default ViewResolver later.
        }
    }

    //No view parser found, read from configuration file
    if (this.viewResolvers == null) {
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

3.5 request process analysis

Through the study of the working principle of tomcat, if you are not familiar with the working principle of tomcat, please see my blog in another column Working principle analysis of high performance service middleware Tomcat (I)_ worn_xiao's blog - CSDN blog ; We know that when the request enters, we all know that the service() method of the Servlet will be called. Through the inheritance level, we can see that we try to search in DispatchServlet , and find No. We went to the parent FrameworkServlet and found it.

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
        super.service(request, response);
    } else {
        this.processRequest(request, response);
    }
}
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processRequest(request, response);
}
protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processRequest(request, response);
}
protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processRequest(request, response);
}

protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
        this.processRequest(request, response);
        if (response.containsHeader("Allow")) {
            return;
        }
    }
    super.doOptions(request, new HttpServletResponseWrapper(response) {
        public void setHeader(String name, String value) {
            if ("Allow".equals(name)) {
                value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
            }
            super.setHeader(name, value);
        }
    });
}
protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    if (this.dispatchTraceRequest) {
        this.processRequest(request, response);
        if ("message/http".equals(response.getContentType())) {
            return;
        }
    }
    super.doTrace(request, response);
}

  super.service(request, response); The core code is shown above. It can be seen that the service method of the parent class is called first OK, let's see what this service() method does

The code in HttpServlet is as follows

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }
            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }
}

You will find that all the code in the parent class calls the doxxx() method of the class However, these methods have been rewritten in our subclass FrameworkServlet This processRequest(request, response); All doxxx() methods in the subclass are processed through the processrequest () method. OK, let's see what this method does

processRequest process request

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    LocalContext 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 {
        //Importantly, doService() is an abstract method that forces subclasses to replicate
        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);
}}

A large area of processing and setting is not our focus. It is mainly the method of doService(), which is an abstract method that forces subclasses to replicate.
So eventually, the subclass DispatcherServlet will certainly replicate the doService() method.

3.5.1 dispatcherServlet.doService()

In the doService() method, the main component distribution processing logic is in the {doDispatch() method. Therefore, we can think that all request processing at the spring MVC level is implemented by the doService() method

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //Print request
    logRequest(request);

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

    //Set the component to the request domain so that other subsequent components can get it
    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 {
        //Key points: the main component distribution processing logic is in the doDispatch() method
        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);
            }
        }
    }
}

As shown in the above code, we won't look at the previous ones. Here we mainly analyze that doDispatch() distributes requests to various components for processing. This method is very important, and the distribution logic is presented here.

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    //The processor and interceptor of this request are combined into an execution chain
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

        try {
            //Check whether it is a file upload request. If so, do some processing
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            //Key: find the processor and interceptor of this request
            mappedHandler = getHandler(processedRequest);
            //Processor processing not found, response 404
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            //Important: find the adapter of the processor in this request
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

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

            //Key: before processing, the responsibility chain mode calls back to the preHandle() method of the interceptor. If it is intercepted, it will not continue
            //Returning true means reassurance, and false means interception
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            //Key points: call the processing method of the adapter, pass it into the processor, and let the adapter convert the processor's results into a unified ModelAndView
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

            //If the default view cannot be found, the default view is set
            applyDefaultViewName(processedRequest, mv);
            //Key points: after the processing is completed, call the postHandle() post-processing method of the 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);
        }
        //Key points: distribute the results, let the view parser parse the view, render the view and data
        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()) {
            //Important: after the view rendering is completed, call the interceptor's afterConcurrentHandlingStarted() method
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

As shown in the above code, let's sort out the distribution steps:

  • getHandler(), get the processor execution chain of this request, including Controller and interceptor, which are combined into an execution chain HandlerExecutionChain.
  • getHandlerAdapter() gets the adapter of the processor. There are many ways to implement the processor, such as directly using Servlet as the processor, implementing the Controller interface, using the Controller annotation, etc. the return values of each interface method are various. Therefore, the adapter mode is used here to uniformly output the return values of the adapter to the processor as ModelAndView.
  •  mappedHandler.applyPreHandle: the responsibility chain mode calls the preHandle() method of the interceptor in the processor chain to represent that the request is ready for processing. The interceptor can intercept processing. If the interceptor intercepts, continue down.
  • ha.handle(), call the processing method of the adapter, pass it into the processor, call the processor interface method, and adapt the processor. The result is ModelAndView.
  • mappedHandler.applyPostHandle, which traverses the postHandle() post-processing method of the interceptor in the calling processor execution chain, represents the request to be processed, but the view has not been rendered
  • processDispatchResult() processes the view and result, calls the view processor, creates the real view, and renders the view data. And after rendering, call the interceptor's afterCompletion() method to represent that the view is rendered.
  • mappedHandler. Applyafterconcurrent handling started: call the interceptor in the processor execution chain to process whether the current request is processed successfully or failed.

The following is an analysis of each of the above steps. getHandler() searches the requested processor object, responsibility chain mode, traverses the handlerMappings collection, finds the processor and interceptor, and will call the getHandler() method of AbstractHandlerMapping. Finally, the processor and interceptor are encapsulated in the handler execution chain object.

getHandlerInternal(), subclass implementation, there are two main implementations. The first is AbstractUrlHandlerMapping. Its subclass simpleurhandlermapping is generally used. This method needs to be configured in the xml configuration file, which is rarely used. The second is AbstractHandlerMethodMapping, which deals with our @ Controller and @ RequestMapping.

Generally, RequestMappingHandlerMapping, a subclass of AbstractHandlerMethodMapping, is used. The annotation query method is isHandler(). If it is analyzed here, it is too long. It will be analyzed in subsequent articles.

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

//The subclass AbstractHandlerMapping of the processor mapping interface implements this method. It is an abstract class, and all HandlerMapping implementation classes inherit from it
//AbstractHandlerMapping only deals with public processes and processes, and extracts abstract methods for subclass implementation, that is, template patterns
public interface HandlerMapping {
    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
        implements HandlerMapping, Ordered, BeanNameAware {
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //Template method, obtain the processor, and implement the specific subclass
        Object handler = getHandlerInternal(request);
        //If not, the default processor is used
        if (handler == null) {
            handler = getDefaultHandler();
        }
        //If there is no default, null will be returned
        if (handler == null) {
            return null;
        }
        //If the processor is of string type, the instance is searched in the IoC container
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        //The processor execution chain is mainly composed of adding interceptors
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        if (logger.isTraceEnabled()) {
            logger.trace("Mapped to " + handler);
        }
        else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
            logger.debug("Mapped to " + executionChain.getHandler());
        }

        if (hasCorsConfigurationSource(handler)) {
            CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            config = (config != null ? config.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }
    
    //Form the processor execution chain and add interceptors
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                //Match with the requested url before adding
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            }
            else {
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }
}

Next, analyze the adaptation components, getHandlerAdapter(),getHandlerAdapter() get the adapter corresponding to the processor, the responsibility chain mode, traverse and call the adapter collection, call the supports() method, and ask each adapter whether it supports the current processor. If true is returned, it means that it is found, stop traversal and return to the adapter.

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            //In the responsibility chain mode, traverse the adapter collection, call the supports() method, and ask each adapter whether it supports the current processor
            //If the traversal is true, it means to stop. If the traversal is found, it returns true
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

Let's take a look at the adapter interface and its subclass, HttpRequestHandlerAdapter, which adapts HttpRequestHandler as the adapter of the handler. SimpleServletHandlerAdapter adapts the Servlet as the adapter of the handler, and SimpleControllerHandlerAdapter adapts the Controller interface as the adapter of the handler

public interface HandlerAdapter {
    //Judge whether the incoming processor supports adaptation
    boolean supports(Object handler);

    //Only when the above supports() method returns true will it be called for adaptation and return the ModelAndView object uniformly
    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    //The function is the same as getLastModified() of Servlet. If the adaptive processor does not support it, return - 1
    long getLastModified(HttpServletRequest request, Object handler);
}

//Adapter for HttpRequestHandler
public class HttpRequestHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof HttpRequestHandler);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }
}

//Adapter for adapting Servlet
public class SimpleServletHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Servlet);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        ((Servlet) handler).service(request, response);
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        return -1;
    }
}

//Adapter for Controller interface
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return ((Controller) handler).handleRequest(request, response);
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }
}

According to the process, after obtaining the corresponding adapter, you can notify the interceptor

3.5.2 interceptor pre notification

Traverse the interceptor chain, call its preHandle() method, and notify the interceptor to intercept and attach before request processing.
If an interceptor returns false, which means interception, the processing flow is interrupted, which means interception.

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

Then, call the handle() method of the adapter to adapt and return ModelAndView. After processing, it also represents that the request is processed in the Controller, and then the interceptor notification is carried out.

3.5.3 post interceptor notification

Unlike the pre notification, the post notification has no interception function and can only be enhanced. The logic also traverses the interceptor chain and calls the postHandle() method of the interceptor.

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

Once the view and data are obtained, you can generate the view and render the data.

3.5.4} result processing

Because of the processing flow of doDispatch(), spring MVC helps us try catch, so we can catch exceptions and pass them into the method. Then, first judge whether an exception is generated in the processing process, and if yes, use the exception handler to handle it. If there is no exception, continue to go down to judge whether rendering is required. If rendering is required, render, and finally call back the interceptor for notification.

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable Exception exception) throws Exception {
    //Show error page
    boolean errorView = false;
    //Handling exceptions
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            //Handling exceptions
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
    //Determine whether the processor needs to return to the view
    if (mv != null && !mv.wasCleared()) {
        //Focus: rendering
        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;
    }
    //When the view rendering is complete, call back the interceptor
    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

//The interceptor callback notifies the interceptor that the view has been rendered and the interceptor can do something more
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
        throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

3.5.5} view parsing and rendering

First, we need to determine whether the view parser is needed for view parsing. Finally, we call the render() method of the parsed view to render the operation. render() method to render.

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);
    //Real view object
    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        //Key: use the view parser to generate the true view
        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 {
        //There is no need to search. The ModelAndView already contains the real view
        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());
        }
        //Important: start rendering
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}

View resolution

  • Traverse the view parser collection. Different views need different parsers to process.
  • ViewResolver parser is an interface. It has several implementation classes corresponding to the supported view technologies.
  • Abstractcacheingviewresolver, an abstract class, supports caching views. All parsers inherit it. It has a Map inside to cache the parsed view objects to solve efficiency problems.
  • UrlBasedViewResolver inherits from AbstractCachingViewResolver. When our Controller returns a string, such as success, it will find the prefix and suffix from our xml configuration file, splice them with the url, and output a completed View address. Another is that when we return the string of redirect: prefix, it will be parsed into the redirected View for redirection.
  • InternalResourceViewResolver, the internal resource resolver, inherits from the above UrlBasedViewResolver, so it has all the functions, which are mainly used to load resources under the / WEB-INF / directory.

There are other parsers that are not commonly used, which will not be introduced here

<!-- Configure view parser -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- View files are from pages Find under folder -->
    <property name="prefix" value="/WEB-INF/pages/"/>
    <!-- File suffix is jsp -->
    <property name="suffix" value=".jsp"/>
</bean>
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
        Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
        //Traverse the view parser collection. Different views need different parsers to process
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

public interface ViewResolver {
    //Try to resolve the view name as a view object. If it cannot be resolved, null is returned
    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

View rendering

  1. After the view is parsed, a view object is generated. View is also an interface, which has the following implementation classes:
  2. AbstractView, the abstract class of View, defines the rendering process and abstracts some abstract methods. Subclasses can be treated specially, and most implementation classes inherit from it
  3. Velocity view, which supports pages generated by the velocity framework.
  4. FreeMarkerView supports pages generated by FreeMarker framework.
  5. JstlView, which supports generating jstl views.
  6. RedirectView, which supports generating page Jump views.
  7. MappingJackson2JsonView, output the view of Jason, and use the Jackson library to implement the Jason sequence
  8. The essence of the view is to write() to the client through the Response object
public interface View {
    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
    String PATH_VARIABLES = View.class.getName() + ".pathVariables";
    String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
    //Content type corresponding to the view
    @Nullable
    default String getContentType() {
        return null;
    }
    //Render data to view
    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception;
}

Finally, the doDispatch() method is notified. After the overall try catch, the finally code block calls the interceptor for final notification.

The interceptor traversed must be the implementation class of the AsyncHandlerInterceptor interface.

void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            if (interceptors[i] instanceof AsyncHandlerInterceptor) {
                try {
                    AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
                    asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
                }
                catch (Throwable ex) {
                    logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
                }
            }
        }

Topics: Java Spring