Spring MVC Source Parsing

Posted by digi24 on Thu, 22 Aug 2019 09:44:53 +0200

Preface

At the beginning of the year, I came across an interview question. When I talked about Spring MVC, I mentioned why the developers of Spring MVC should design father-son containers, or what is the more practical function of father-son containers?
First, understand that for a web application, when deployed on a web container, the container provides a global context, ServletContext, which will provide a host environment for subsequent Spring.

Spring MVC workflow

Dispatcher Servlet context inheritance

Father and Son Containers Designed by Spring MVC

Father and Son Container Profile

--stay web.xml In configuration, two important xml:applicationContext.xml and SpringMVC-conf.xml
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:applictionContext.xml</param-value>
</context-param>
<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  
<servlet>
    <servlet-name>dispatcher-servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:springMVC-conf.xml</param-value>
    </init-param>
</servlet>
  
<servlet-mapping>
  <servlet-name>dispatcher-servlet</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

Design Purpose of Father and Son Containers

According to the official explanation of Spring MVC, the parent container mainly includes some beans of basic scaffolding, such as Pool, DataSource, Dao and Service. The goal is to share between different Servlet instances. These different beans can be overridden in subcontainers.
Subcontainers mainly include some web-related bean s such as Controller, View, etc.  

Dispatcher Servlet Source Code Analysis

Since both Spring and Spring MVC containers are included in Spring MVC, when are the two containers initialized?

Root container initialization

First, the root container is created through the ServletContext listener, the default listener is ContextLoaderListener, which calls the contextInitialized method of the listener when the web application starts.
Start with the ContextLoaderListener class, which Spring officially describes as starting a listener to start and close Spring's root Web Application Context.
     

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    //=== Initialize root Web Application Context===
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

   

//ContextLoader.java
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //When the Spring container is initialized, an exception is thrown if the root Spring container already exists in the servlet container, proving that the root Web Application Context can only have one.
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    try {
        //Create a Web Application Context instance
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                //Configure Web Application Context
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        
        /**
           Set the generated Web Application Context to root Web Application Context. Save in the ServletContext context.
           The next step in initializing MVC Application Context is to take the root context from the ServletContext as its parent context.
        **/ 
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }
        
        return this.context;
    }
    catch (RuntimeException | Error ex) {
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}

The above code mainly completes two functions: creating an instance WebApplicationContext instance, setting the created WebApplicationContext to the root context, that is, setting the value of ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE.

MVC container initialization

As you know, the lifecycle of servlets starts with the init method, ends with the desctory method, and the jvm is responsible for garbage collection. Dispatcher Servlet is also a common Servlet. First, look at the inheritance graph of Dispatcher Servlet, and have an understanding of the whole inheritance relationship.

Now that we talk about servlets, let's start with the init method of servlets.

//HttpServletBean.java
@Override
public final void init() throws ServletException {

    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) {
            throw ex;
        }
    }

    //Give subclass overrides
    initServletBean();
}

//FrameworkServlet.java
@Override
protected final void initServletBean() throws ServletException {
    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        throw ex;
    }
}

//FrameworkServlet.java
//Initialize MVC container
protected WebApplicationContext initWebApplicationContext() {
    //Remove the root context from the ServletContext
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    
    //If there is no web Application Context, create a web Application Context
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }

    //Subclasses customize subsequent actions on servlet context, implemented in Dispatcher Servlet
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            //Execute subclass extension method onRefresh to initialize all web-related components in Dispatcher Servlet
            onRefresh(wac);
        }
    }

    //Publish servlet context to ServletContext
    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        //Register the servlet context with the attribute name of org. springframework. web. servlet. Framework Servlet. CONTEXT. + servletName in the ServletContext
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    //Get the Web Application Context implementation class, which is actually Xml Web Application Context
    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");
    }
    
    //Generate an Xml Web Application Context instance
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    //Set the root container as the parent container 
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        //Setting up configuration files
        wac.setConfigLocation(configLocation);
    }
    
    //Configuring Web Application Context
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    
    //Start processing bean s
    wac.refresh();
}

The key code above is in the Framework Servlet class. There are several key points: remove the root context, create the context and set the parent context, complete the refresh, and publish the context to the ServletContext. At this point, it can be said that the sub-container (context) has been created. The onRefresh method is used to rewrite the onRefresh method by the Dispatcher Servlet, which returns to the initStrategies method we are familiar with.

web component initialization

 

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

protected void initStrategies(ApplicationContext context) {
    //File Upload Parser
    initMultipartResolver(context);
    
    //Locale resolver
    initLocaleResolver(context);
    
    //theme resolver
    initThemeResolver(context);
    
    //Processor mapper (mapping of url and Controller methods)
    initHandlerMappings(context);
    
    //Processor adapter (actually executing the Controller method)
    initHandlerAdapters(context);
    
    //Processor exception parser
    initHandlerExceptionResolvers(context);
    
    //RequestToViewName parser
    initRequestToViewNameTranslator(context);
    
    //View parser (view matching and rendering)
    initViewResolvers(context);
    
    //FlashMap Manager
    initFlashMapManager(context);
}

Here we focus on three important components: Handler Mapping, Handler Adapter and ViewResolver. Before analyzing these three components, let's first look at our spring mvc-conf.xml configuration file. In the mvc configuration file, we configure two lines of code:

<context:component-scan base-package="com.zhangfei"/>
<mvc:annotation-driven>

The second line of code adds the default AndleMapping, ViewResolver, HandleAdapter. Let's look at the source code definition of annotation-driver. According to spring's custom schema definition, we find the following code, as shown in the figure:

This file has one line of code:

http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
//All label parsers in MVC are defined here.
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
        registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
        registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
        registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
        registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
    }
}

By analyzing AnnotationDrivenBeanDefinitionParser class, the following three components are assembled:

Initialization Processor Mapper

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    //Here the default value of detectAllHandler Mappings is true, which can be set to false through the configuration file.
    if (this.detectAllHandlerMappings) {
        //Find all HandlerMapping implementation classes from context (including parent context)
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            //Here only fixed bean s are taken
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            
        }
    }
    
    /***
      Make sure that there is at least one Handler Mapping, and if it is not found, register a default
      The default rule is in Dispatcher Servlet. properties, where you take BeanNameUrlHandler Mapping, Request Mapping Handler Mapping
    ***/
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);     
    }
}

Initialize processor adapter

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

    if (this.detectAllHandlerAdapters) {
        //Find all HandlerAdapter implementation classes from context (including parent context)
        Map<String, HandlerAdapter> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            //Here the bean name is handler adapter and the handler adapter type is handler adapter.
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            
        }
    }
    
    /**
    If not, remove the specified three implementation classes from the default rule: HttpRequestHandler Adapter, SimpleController Handler Adapter, RequestMapping Handler Adapter
    **/
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);     
    }
}

Initialization attempt parser

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

    if (this.detectAllViewResolvers) {
        //Find all ViewResolver implementation classes from context (including parent context)
        Map<String, ViewResolver> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else {
        try {
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
            
        }
    }
    
    /**
    If not, remove the specified implementation class from the default rule: Internal ResourceViewResolver
    **/
    if (this.viewResolvers == null) {
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); 
    }
}

The getDefaultStrategies method is called when the initialization of the three components is finally judged to be NULL, that is, the specified default value is taken from Dispatcher Servlet. properties.

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
                throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className +"] for interface [" + key + "]", ex);
            }
            catch (LinkageError err) {
                throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" +className + "] for interface [" + key + "]", err);
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<>();
    }
}

Dispatcher Servlet request processing

Referring to the request processing process, let's review the Servlet life cycle again. Processing requests are all processed in the service method. Then we start with the service method of Dispatcher Servlet. The Dispatcher Servlet inherits the Framework Servlet and overrides the service, doGet, doPost, doPut, doDelete methods in the Framework Servlet.

//FrameworkServlet.java
@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);
    }
}

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

    
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
}

    
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
}

    
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
}


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());
    
    //Bind the newly constructed LocaleContext object and ServletRequestAttributes object to the current request thread (unbound later)
    initContextHolders(request, localeContext, requestAttributes);

    try {
        //The abstract method is handed over to the Dispatcher Servlet method for implementation
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        //Reset the LocaleContext and RequestAttributes objects, that is, unbind the LocaleContext objects and ServletRequestAttributes objects from the current request thread
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        //Publish ServletRequestHandled Event Event Event Event
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}
//DispatcherServlet.java
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    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));
            }
        }
    }
    
    //Fill in the current request object with four attributes
    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 {
        //Mainly handles distribution requests
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}


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);
            
            //Call handler Mapping to get handler Chain
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            //Get the Handler Adapter that supports the handler parsing
            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;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            //Using handler Adapter to Complete handler Processing
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

            //View processing (page rendering)
            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        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()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

Dispatcher Servlet's doDispatch method can be summarized as follows: Firstly, according to the current request path, find the corresponding Handler Method, a Handler Method and several interceptors to construct a Handler Execution Chain. Handler Adapter object is obtained through Handler Execution Chain, and handler Ad is executed. The handle method of apter obtains the ModelAndView object, calls the ModelAndView to parse the view, renders the view, and ends the Response.

Reference resources

https://juejin.im/post/5cb89dae6fb9a0686b47306d
https://juejin.im/post/5cbc10b46fb9a0689f4c2c22
https://www.cnblogs.com/fangjian0423/p/springMVC-dispatcherServlet.html

Topics: Java Spring xml jvm