Spring MVC workflow from the perspective of source code

Posted by iSpeedDevil! on Sat, 29 Jan 2022 09:19:30 +0100

Spring MVC workflow from the perspective of source code

1.1 build a source code debugging environment for gradle

1) Build gradle environment
4 steps
1,File-New-Module
Select java and web


2. Fill in package information

3. Storage path

2) Increase start dependence
The dependent items can be directly copied and pasted
1. Dependence on spring
2. Dependence on MVC
3. Dependency on Tomcat plug-in
build.gradle

group 'com.spring.test'
version '5.0.2.RELEASE'

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'com.bmuschko.tomcat' //tomcat: plug in
// tomcat: the following configuration will download the plug-in binary at the first startup
//Execute gradle tomcatRun in the root directory of the project
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.bmuschko:gradle-tomcat-plugin:2.5'
    }
}
// Configure Ali source
allprojects {
    repositories {
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
}


dependencies {
    testCompile group: 'org.testng', name: 'testng', version: '6.14.3'
    runtime 'javax.servlet:jstl:1.1.2' // Servlet container required
    compile(project(':spring-context'))
    compile(project(':spring-web'))
    compile(project(':spring-webmvc'))

    // Tomcat: add Tomcat runtime library to Tomcat configuration: (here is Tomcat9)
    def tomcatVersion = '9.0.1'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
            "org.apache.tomcat.embed:tomcat-embed-logging-juli:9.0.0.M6",
            "org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}

// Tomcat: some protocol settings (note that it must be added here, otherwise the exception of Tomcat will be thrown, only tomcat9)
tomcat {
    httpProtocol = 'org.apache.coyote.http11.Http11Nio2Protocol'
    ajpProtocol  = 'org.apache.coyote.ajp.AjpNio2Protocol'
}



// UTF-8
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}


Premise:
Add WEB-INF directory and web xml
1. Open file - proect structtrue
2. Select the mvc project, expand it, select web gradle, and click the plus sign on the right
3. Confirm path
spring-mvc-test\src\main\webapp\WEB-INF\web.xml

WEB-INF and xml are created

webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- Spring MVC to configure -->
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:mvc-servlet.xml</param-value>
            <!--<param-value>/WEB-INF/mvc-servlet.xml</param-value>-->
        </init-param>
        <!-- load-on-startup Whether the element tag container loads this at startup servlet(Instantiate and call its init()method) -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

resources/mvc-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- Enable annotation scanning -->
    <context:component-scan base-package="com.spring.mvc.test"/>
    <!-- View parser object -->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <!--<property name = "prefix" value="/WEB-INF/"></property>-->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- open SpringMVC Framework annotation support -->
    <mvc:annotation-driven/>
    <!--Static resources(js,image etc.)Visit-->
    <mvc:default-servlet-handler/>

</beans>

webapp/index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>SpringMvc In depth analysis of source code</title>
</head>
<body>
hello ! ${username}
</body>
</html>


MvcController.java

package com.spring.mvc.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MvcController {

	@RequestMapping("/index")
	public ModelAndView getModeAndView() {
		//Create a model view object
		System.out.println("123");
		ModelAndView mav = new ModelAndView("index");
		mav.getModelMap().put("username","administrator");
		return mav;
	}
	@RequestMapping("/text")
	@ResponseBody
	public String text() {
		return "Text...";
	}

}


4) Start MVC project
Two startup modes
Method 1: plug in startup
Start outside the idea environment (run gradle + task name under the project root directory)

Task NameDescription
tomcatRunStart the Tomcat instance and deploy the Web application to it.
tomcatRunWarStart the Tomcat instance and deploy the WAR.
tomcatStopStop Tomcat instance
tomcatJasperRun the JSP compiler and use Jasper to convert JSP pages into Java source code.

Execute gradle tomcatRun in the root directory of the project

#Move the Tomcat instance and deploy the Web application to it 
gradle tomcatRun 
#Stop Tomcat instance 
gradle tomcatStop

Console normal output

Mode 2: integrated into idea and started


Click Run

Run successfully

Mode 3:
Find gradle's task on the right side of idea and double-click it directly. It's cool~

Access MVC project
Note: spring test MVC is the name of the project
http://localhost:8080/spring-test-mvc/index

The effect is as follows

5) Source debugging configuration
How to enable debug mode in gradle environment
important
To debug, you need to use method 2 above, because the environment variable of gradle needs to be set for debug startup,
That is, insert some parameter commands when running the gradle command.
Add the debug parameter to expose the 5005 port, that is, listen to the 5005 port.

-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005


Configure Remote in; Monitor 5005 port
Click the + sign to create Remote; The default configuration is OK

Last step
1. Run tomcat first
2. Running remote

http://localhost:8080/spring-test-mvc/index

Hit the breakpoint and try!
Including the creation of bean s in our previous ioc and other places, just call.

1.2 working principle and inheritance relationship of MVC

1) Working principle of MVC bottom layer
Objective: to understand the working principle of spring MVC (compared with the source code), and how to find the corresponding Controller for page rendering
Step 11
source: http://localhost:8080/spring-test-mvc/index

How spring MVC works
Note that the request is not directly sent to the dispatcher servlet (dispatcher), but is forwarded by tomcat. After processing, it is forwarded to tomcat
1. Dispatcher servlet (front-end controller) is a servlet, which is responsible for receiving requests and forwarding them to the corresponding processing components.
2. HanlerMapping (processor mapper) is a component that completes url to Controller mapping in spring MVC. DispatcherServlet finds the Controller that handles the Request from HandlerMapping
3. HanlerMapping returns an actuator chain (a component mapped from url to Controller) to DispatcherServlet
4. Dispatcher servlet request processor adapter HandlerAdapter
5. The processor adapter handler adapter accesses our handler (controller)
6. The handler (controller) returns the ModelAndView to the handler adapter
7. Processor adapter HandlerAdapter returns ModelAndView to DispatcherServlet
8. DispatcherServlet requests ViewResolver view resolver
9. ViewResolver the view parser returns the view to the dispatcher servlet
10. DispatcherServlet requests view to do page parsing and rendering
11. view returns the rendered data to DS, and DS streams the rendered characters to client. You see the page!

2) MVC core class inheritance relationship
Objective: to simply understand the inheritance relationship of MVC

DispatcherServlet front-end general controller (webmvc source code)
FrameworkServlet (webmvc source code)
HttpServletBean is a simple extension class of (webmvc source code)
HttpServlet (servlet API, which has left the control scope of spring mvc)

1.3 in depth analysis of spring MVC source code

Current source code explanation ideas
1. Breakpoint debugging
2. Flow chart comparison
3. Inheritance relationship comparison

1.3.1 MVC startup phase

Note that there is no way to debug at this stage. Let's look at the source code directly from the servlet specification
In the following request phase, the complete process of debug ging the request link will be described in detail

web.xml review

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- Spring MVC to configure -->
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:mvc-servlet.xml</param-value>
            <!--<param-value>/WEB-INF/mvc-servlet.xml</param-value>-->
        </init-param>
        <!-- load-on-startup Whether the element tag container loads this at startup servlet(Instantiate and call its init()method) -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

From the above configuration, we can see that web DS in XML is a servlet. Let's start with the servlet specification of java web
As mentioned above, in the category of springmvc, the top level is the standard HttpServlet inherited by HttpServlet bean
1. ioC Bean initialization
org.springframework.web.servlet.HttpServletBean#init
2. 9 major component initialization (ioC)
org.springframework.web.servlet.HttpServletBean#init

Summary: method call relationship (pseudo code)

HttpServletBean{
    init(){ 
        protected initServletBean(); 
    } 
  }
  FrameworkServlet extends HttpServletBean{ 
    @Override 
    initServletBean(){ 
      initWebApplicationContext(){ 
        WebApplicationContext wac = createWebApplicationContext(rootContext); 
        protected onRefresh(wac); 
      } 
    } 
  }

 DispatcherServlet extends FrameworkServlet{ 
   onRefresh(wac){ 
     initStrategies(wac){ 
        //Multi file upload component 
        initMultipartResolver(context); 
        //Initialize locales 
        initLocaleResolver(context); 
        //Initialize template processor 
        initThemeResolver(context); 
        //Initialize processor mapper 
        initHandlerMappings(context); 
        //Initialize processor adapter 
        initHandlerAdapters(context); 
        //Initialize exception interceptor 
        initHandlerExceptionResolvers(context); 
        //Initialize view preprocessor 
        initRequestToViewNameTranslator(context); 
        //Initialize view converter 
        initViewResolvers(context); 
        //FlashMap Manager 
        initFlashMapManager(context); 
     } 
    } 
 }
        

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware

//Function: 	 Spring MVC initialization configuration, the first method to be executed
	//Goal: set up Web contextConfigLocation attribute configured in XML
	//Focus: initServletBean();
	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		//Get web The contextConfigLocation configuration attribute in XML is the configuration (servlet) file of spring MVC
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);  // ===>
		if (!pvs.isEmpty()) {
			try {  //Here are a bunch of initialization operations. Regardless of them, the focus is on the following!
				//Returns an instance of BeanWrapperImpl
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				//Obtain various information of the server
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				//The template method can be called in the subclass and do some initialization work. bw stands for DispatcherServelt.
				initBeanWrapper(bw);
				//Set the configured initialization value to the dispatcher servlet
				bw.setPropertyValues(pvs, true);
			} catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		//Initialization operation, abstract. Subclass implementation; [Focus]!!!!!!! (ioC, 9 components)
		initServletBean();   // ===>
		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware

//bean initializing servlet
	//Focus: initWebApplicationContext
	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Initialize context here!! [important node]!!!!! (ioC, 9 components)
			this.webApplicationContext = initWebApplicationContext();   // ===>
			initFrameworkServlet();  // Empty, regardless of him
		} catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		} catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware

//Objective: the main logic is to initialize the IOC container and 9 components, and finally call the refresh() method
//Focus: createWebApplicationContext and onRefresh
	protected WebApplicationContext initWebApplicationContext() {
		// Call WebApplicationContextUtils to get the root context, which is saved in ServletContext
		// The spring xml loaded through the servlet's application listener is the parent container
		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()) {
					// 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
			// Find the bound context. If any, use it directly
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			//If not, start creating and insert the rootcontext as the parent container.
			wac = createWebApplicationContext(rootContext);   // ===>
		}

		if (!this.refreshEventReceived) {
			//9 major components!! [key steps]!!!!
			onRefresh(wac);  // ===>
		}
		if (this.publishContext) {
			// Save the currently established context into ServletContext, and the attribute name used is related to the current Servlet name
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

The next step should be initialization, which will enter the createWebApplicationContext method

//Focus on configureAndRefreshWebApplicationContext
	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		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");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		// Set parent context (i.e. root context)
		wac.setParent(parent);
		// contextConfigLocation attribute!
		// That is, web xml file of spring MVC configured in xml
		String configLocation = getContextConfigLocation();  // ===>Configuration xml file
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		//Configure and refresh the web application context (refresh
		configureAndRefreshWebApplicationContext(wac);   // ===>IOC load context

		return wac;
	}

Finally, go to configureAndRefreshWebApplicationContext to initialize

//	Complete the parsing, loading and initialization of all bean s
	//Focus: WAC refresh()
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		//Up a pile of settings, regardless of him, the focus is at the bottom!
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			if (this.contextId != null) {
//				The id of the generated WebApplicationContext is used to load the spring MVC configuration file later
				wac.setId(this.contextId);
			} else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}
		//Set Servlet
		wac.setServletContext(getServletContext());
		//Set Servlet configuration information
		wac.setServletConfig(getServletConfig());
		//Set namespace
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}
		//Post enhancement is generally empty
		postProcessWebApplicationContext(wac);
		//Initialization, regardless of him
		applyInitializers(wac);
		//[key] called the refresh of ioc! Refer to the ioc source code course!!!!!!!!
		wac.refresh();  // ===>Enter the world of ioc! Complete bean loading
	}

Then there's the old man, IOC

// Template method (refresh template defined under abstract class), core method
	//Breakpoint view
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		// synchronized block lock (monitorenter --monitorexit)
		// Otherwise, before refresh() is over, another operation will start or destroy the container
		//	 The startupShutdownMonitor is an empty object, lock
		synchronized (this.startupShutdownMonitor) {
			//1. Prepare to refresh: several variables are set, which is also preparation work
			prepareRefresh();   //  ===>
			// 2. [get new bean factory] key steps, key points!
			//2.1. Close the old BeanFactory
			//2.2. Create a new BeanFactory (DefaluListbaleBeanFactory)
			//2.3. Parse xml / load Bean definitions and register Bean definitions to beanfactory (not initialized)
			//2.4. Return to the new factory
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  //  ===>The good play begins
			//3. [bean factory pre operation] configure container features for BeanFactory
			// For example, class loader, expression parser, registered default environment bean and post manager
			prepareBeanFactory(beanFactory);   // ===>
			try {
				// 4. [bean factory post operation] this is an empty method. If the subclass needs it, implement it yourself
				postProcessBeanFactory(beanFactory);  // ===>Empty!

				//5. [call bean factory postprocessor] and start calling our own interface
				//Objectives:
				//Call sequence 1: define the bean first and register the processor later
				//Call sequence 2: Post bean factory post processor
				invokeBeanFactoryPostProcessors(beanFactory);  // ===>Important play

				//6. [register bean postprocessor] is only registered, but it will not be called yet
				//Logic: find out all classes that implement the BeanPostProcessor interface, classify, sort and register
				registerBeanPostProcessors(beanFactory);  // ===>Key points
				// Initialize message source for this context.
				//7. [initialization message source] internationalization problem i18n, refer to https://nacos.io/
				initMessageSource(); // ===>Is to add a single bean to the factory

				// Initialize event multicaster for this context.
				//8. [initialize event broadcaster] initialize the customized event listening multiplexer
				// If you need to publish an event, call its multicast event method
				// To broadcast events to listeners is actually to start a thread to process and throw events to listeners for processing
				// (it can be verified through the code of simpleapplicationeventmulticast)
				initApplicationEventMulticaster(); // ===>Similarly, a bean is added


				// 9. [refresh] this is a protected empty method, which is implemented by specific subclasses
				//  You can initialize some special beans here
				// (before initializing singleton beans)
				onRefresh();  // ===>Empty! Usually no one cares about it


				//10. [register listener], the listener needs to implement the ApplicationListener interface
				// That is, scan these classes that implement the interface and put them into the list of broadcasters
				// In fact, it is an observer mode. When the broadcaster receives the call of the event, it will cycle the listeners list,
				// Call their onApplicationEvent methods one by one and throw event to them.
				registerListeners();  // ===>Observer mode


				//11. [end bean factory initialization]
				//1. Initialize all singleton beans and generate objects / fills by reflection
				//2. Call the pre processor and post processor of the Bean
				// Key point: completed in getBean method
				finishBeanFactoryInitialization(beanFactory);  // ===>Key points!


				// 12. End refresh operation
				// Publishing events and clearing context
				finishRefresh();


			} catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				// Destroy the created beans so that some beans will always occupy resources
				destroyBeans();

				// Reset 'active' flag.
				// Cancel the refresh operation and reset the synchronization ID of the container.
				cancelRefresh(ex);

				// Throw the anomaly out
				throw ex;
			} finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

Here is one point, calling finishRefresh() after initialization.
After refresh is completed, an event is sent to complete initialization

protected void finishRefresh() {
		// 1. Clearing the cache is actually a clear meal
		clearResourceCaches();

		// 2. The initialization of the LifecycleProcessor interface is to register a bean of LifecycleProcessor
		// ps: when the ApplicationContext starts or stops, it will pass through the lifecycle processor
		// To update the status with the cycles of all declared bean s
		// The lifecycle processor needs to be initialized before it is used
		initLifecycleProcessor();

		// 3. Start all bean s that implement the lifecycle processor interface
		//Call their start methods one by one. Usually no one uses it
		getLifecycleProcessor().onRefresh();

		// 4. Publish a ContextRefreshedEvent event
		//Announce that refresh is complete. If you need a listener, deal with it. Generally, no one cares
		publishEvent(new ContextRefreshedEvent(this));

		// 5. Register the current container with MBeanServer, jmx will use
		LiveBeansView.registerApplicationContext(this);
	}

Then the corresponding event is received in the FrameworkServlet

public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		onRefresh(event.getApplicationContext());
	}
@Override
	protected void onRefresh(ApplicationContext context) {
		//Initialization policy
		initStrategies(context);  // ===>
	}

Final initialization

//One meal initialization... [core steps]
	// In fact, it is to pull out the bean s of the corresponding type from the previously initialized context, and then
	// Fill in the corresponding attributes in DS to facilitate the use of the following request stage

	// So far, in fact, the core start-up stage has been completed, and everything you need has been provided.

	protected void initStrategies(ApplicationContext context) {
		//Multi file upload component
		initMultipartResolver(context);
		//Initialize locales
		initLocaleResolver(context);
		//Initialize template processor
		initThemeResolver(context);
		//Initialize processor mapper
		initHandlerMappings(context);
		//Initialize processor adapter
		initHandlerAdapters(context);
		//Initialize exception interceptor
		initHandlerExceptionResolvers(context);
		//Initialize view preprocessor
		initRequestToViewNameTranslator(context);
		//Initialize view converter
		initViewResolvers(context);
		//FlashMap Manager
		initFlashMapManager(context);
	}

1.3.2 MVC request phase

Requirements: we input in the browser http://localhost:8080/spring -Test MVC / index, what has been done behind it
Objective: how can MVC find our controller through a url and return data
1. Breakpoint debugging 2. Flow chart comparison 3. Inheritance relationship comparison
Flow diagram:
Code lookup path:

spring mvc's FrameworkServlet, which is the entry of our source code tracking

Project start
visit

http://localhost:8080/spring-test-mvc/index

Entrance: open the requested door
org.springframework.web.servlet.FrameworkServlet:

//MVC request entry (browser)

//For example, we enter in the browser http://test.io/get , what has been done behind the scenes
//Objective: how can MVC find our controller through a url and return data
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//Whether the request gets
		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		//Judge the request mode and call the service() method in the parent class (HttpServlet) if it is not the PATCH mode
		if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
			processRequest(request, response);
		} else {
//			Call the service method in the parent class,
// Call the service of the parent class, and then call the current class doGet!!!!!!!!!
			super.service(request, response);  // ===>Set out~
		}
	}

The java web standard tells us that the get of the request will be given to the doGet method of the standard HttpServlet
This class, FrameworkServlet, is a subclass of HttpServlet and covers the above doGet,
So, request to enter the first entrance of spring, right here!!!
1)org.springframework.web.servlet.FrameworkServlet#doGet
Called org springframework. web. servlet. FrameworkServlet#doGet

//The get request call overrides the parent class, which is the doGet of the standard HttpServlet
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);    // ===>Get is transferred to process for processing
	}

2)org.springframework.web.servlet.FrameworkServlet#processRequest

//	Focus: doService
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
// Get the current timestamp of the system
		long startTime = System.currentTimeMillis();
		// Declare an exception top-level object Throwable
		Throwable failureCause = null;
// Localization and internationalization
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		// Build a local internationalization context, simplelocal context
		LocaleContext localeContext = buildLocaleContext(request);
		// Gets the RequestAttributes currently bound to the thread
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		// Build ServletRequestAttributes
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
		// Put localeContext and requestAttributes into the current thread,
		// Each request thread in the request scope is different. Parameters and attribute s need to be passed directly in the context
		// (review: request, session, application)
		initContextHolders(request, localeContext, requestAttributes);  // ===>

		try {
			//There is a lot of preparatory work ahead. The key point is here! Jump to the DispatcherServlet class (subclass override) [key!]
			doService(request, response);  // ===>Enter the world of DS!
		} 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();
			}

			if (logger.isDebugEnabled()) {
				if (failureCause != null) {
					this.logger.debug("Could not complete request", failureCause);
				} else {
					if (asyncManager.isConcurrentHandlingStarted()) {
						logger.debug("Leaving response open for concurrent processing");
					} else {
						this.logger.debug("Successfully completed request");
					}
				}
			}

			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

3)org.springframework.web.servlet.DispatcherServlet#doService

	//Hand over the request to doDispatch for processing, and focus on doDispatch(request, response);
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(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
		// Throw a pile of things into the request
		// web context, you can use request. Com on the subsequent request link Getattribute is taken out for use
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // Localization processor
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);  // Topic processor, 2003
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // Subject resource file

		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 {
			//Focus, core!!!!!!!!!!!!!!!!!!!!
			doDispatch(request, response);  // ===>No, come on!
		} finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

Enter the core
4)org.springframework.web.servlet.DispatcherServlet#doDispatch

//	The core code of Spring MVC
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			//Create view object
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				//Request check, whether file upload request (binary request)
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

//				Get a Handler according to the current request Find the processor mapper and enter!!!!!!!!!!!
				mappedHandler = getHandler(processedRequest);  // ===>Legend step 2-3
				if (mappedHandler == null) {
					// If the current request is not handled by a handler, an exception is thrown
					noHandlerFound(processedRequest, response);
					return;
				}

				// handler adapter, find: RequestMappingHandlerAdapter. You can view the type through the debug variable
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  // ===>Legend step 4

				// get method
				String method = request.getMethod();
				//get method is true
				boolean isGet = "GET".equals(method);
				//method is get
				if (isGet || "HEAD".equals(method)) { // Processing last modified, browser cache time
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				//pre method corresponding to cyclic interceptor
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {   // ===> applyPreHandle
					return;
				}

				// Let the handlerAdapter execute the business controller method and really enter the entry of the controller!
				// com.spring.mvc.test.MvcController.getModeAndView
				// Let it return the modelAndView object! debug carefully check the value of mv variable
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  //  ===>Controller entrance, get up~

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				// Set the default view when mav is returned but no specific view is set
				applyDefaultViewName(processedRequest, mv);  // ===>
				mappedHandler.applyPostHandle(processedRequest, response, mv);  // ===>postHandle called after interceptor
			} 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);
			}
			//1. Request the view parser and parse it into a view. The example is shown in steps 8-9
			//2. Execute page rendering (jsp), as shown in step 10-11
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);  // ===> go!
		} 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);
				}
			}
		}
	}

getHandler
@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//More than one, such as BeanNameHandlerMapping, simpleurhandlermapping,
		// And the RequestHandlerMapping we need
		if (this.handlerMappings != null) {
			for (HandlerMapping hm : this.handlerMappings) {
				if (logger.isTraceEnabled()) {
					logger.trace(
							"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
				}
				//This is the actuator chain, which contains the controller that needs to process the request
				HandlerExecutionChain handler = hm.getHandler(request);  // ===>[key points] how to get the controller according to the url?
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);  // ===>Where to find the controller and method!
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {  // If it is a string, get the corresponding bean from the factory
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		//The so-called handler chain is here! In fact, it's just packaging. Add the interceptor list. For details, see the source code structure of HandlerExecutionChain!
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);  // ===>
		if (CorsUtils.isCorsRequest(request)) { //If it is a cross domain request (add Origin from the header)
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config); //Just add an interceptor to it
		}
		return executionChain; // handler chain returns to DS
	}
@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);  // Requested url value
		if (logger.isDebugEnabled()) {
			logger.debug("Looking up handler method for path " + lookupPath);
		}
		this.mappingRegistry.acquireReadLock(); //Add a read lock, that is, mapping can be modified!
		try {
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);  // ===>Hide here!
			if (logger.isDebugEnabled()) {
				if (handlerMethod != null) {
					logger.debug("Returning handler method [" + handlerMethod + "]");
				}
				else {
					logger.debug("Did not find handler method for [" + lookupPath + "]");
				}
			}
			//Return, deal with it before you return. If it is a string, find the corresponding bean and return
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); // ===>
		}
		finally {
			this.mappingRegistry.releaseReadLock(); //Release it!
		}
	}
@Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		//Define a collection that stores all the methods on the match
		List<Match> matches = new ArrayList<>();
		// mappingRegistry, the mapping relationship component scan is swept in! debug and pay attention to the mappingLookUp
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {  // directPathMatches. There may be multiple matches on all matches
			addMatchingMappings(directPathMatches, matches, request);  // Sort them out and put them in the list of matches
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			Collections.sort(matches, comparator); //Sort multiple matching method s according to the defined comparator
			if (logger.isTraceEnabled()) {
				logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
						lookupPath + "] : " + matches);
			}
			Match bestMatch = matches.get(0); // Take the one with the highest matching priority
			if (matches.size() > 1) {
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {  // If the first and second matching priorities are the same
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
							request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");  // Throw an exception and prompt for repetition
				}
			}
			handleMatch(bestMatch.mapping, lookupPath, request); // ===>Nothing. The mapping relationship sets an attribute in the request
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}

Interceptor

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
		//Returning the handler chain actually wraps the interceptors of handler and MappedInterceptor types into an object
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}
getHandlerAdapter
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {

		//Initialize initHandlerAdapters in 9 major components, which is in onRefresh method of this class
		if (this.handlerAdapters != null) {
			for (HandlerAdapter ha : this.handlerAdapters) {
				if (logger.isTraceEnabled()) {
					logger.trace("Testing handler adapter [" + ha + "]");
				}
				if (ha.supports(handler)) {
					return ha;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

Call business Controller (core)

Execute our business controller method, com spring. mvc. test. MvcController. getModeAndView mv = ha. handle(processedRequest, response, mappedHandler.getHandler());

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInterna

@Override
	protected boolean supportsInternal(HandlerMethod handlerMethod) {
		return true;
	}

//	Start calling service controller
	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// Start calling the service controller!!!!!!!!!!!!!!!!!!!!!
			mav = invokeHandlerMethod(request, response, handlerMethod);  // ==> go!
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}

@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		// Balabala, a pile of pre set preparation operations, regardless of him, look down!
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);

			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				if (logger.isDebugEnabled()) {
					logger.debug("Found concurrent result value [" + result + "]");
				}
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}
        //Reflection calling service controller starts!!! [key point]!!!!!!
			invocableMethod.invokeAndHandle(webRequest, mavContainer);  // ===>Reflection call
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
        //Reflection call
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);  // ===>
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
			}
			throw ex;
		}
	}
org.springframework.web.servlet.DispatcherServlet#processDispatchResult (view parsing and rendering)
//1. Request view parser to parse into view
	//2. Perform page rendering (jsp)
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
									   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
									   @Nullable Exception exception) throws Exception {

		boolean errorView = false;
		//If there is an exception, handle the exception, which is the default error page we usually do
		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);// In fact, it is to be handled by the default handler of the system
				errorView = (mv != null);
			}
		}


		if (mv != null && !mv.wasCleared()) {
			//View parser + rendering!!! [key points]
			render(mv, request, response);  // ===>Get in!
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		} else {
			if (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

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

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}
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);  // Localized Information 

		View view;
		//viewName=index, which is the path string in the mav returned by the controller
		String viewName = mv.getViewName();
		if (viewName != null) {
			// Get the view parser, parse it into view (jstlview), and render it below,
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);   // ===>Are you ready? The level is very deep
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		} else {
			// No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
			if (view == null) {
				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
						"View object in servlet with name '" + getServletName() + "'");
			}
		}

		// Delegate to the View object for rendering.
		if (logger.isDebugEnabled()) {
			logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());  // If there is an exception code, set it to response
			}
			//Perform page rendering, abstractview Render [key point]
			view.render(mv.getModelInternal(), request, response);  // ===>Figure 10-11: render the view and throw it to response
		} catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
						getServletName() + "'", ex);
			}
			throw ex;
		}
	}

Specific analysis

	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		// Note that when debugging, if the same view is requested, there will be a cache. What should I do?
		// We put a line at the beginning of him to make him create forever,
		// Note that the following line is added by ourselves for debugging! The original code starts with if

		createView(viewName, locale); // ===>I added it myself. debug goes in from here

		if (!isCache()) {
			return createView(viewName, locale);
		}
		else {
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			if (view == null) {
				synchronized (this.viewCreationCache) {
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) {
						// Ask the subclass to create the View object.
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
						if (view != null) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
							if (logger.isTraceEnabled()) {
								logger.trace("Cached view [" + cacheKey + "]");
							}
						}
					}
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

@Override
	protected View createView(String viewName, Locale locale) throws Exception {
		// If this resolver is not supposed to handle the given view,
		// return null to pass on to the next resolver in the chain.
		if (!canHandle(viewName, locale)) {
			return null;
		}
		// Check for special "redirect:" prefix.
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {  // redirect: the path at the beginning of redirection!
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
				view.setHosts(hosts);
			} // Set a bunch of redirection information, url, host, etc
			return applyLifecycleMethods(viewName, view);
		}
		// Check for special "forward:" prefix.
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {   // Similarly, the beginning of forward:
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			return new InternalResourceView(forwardUrl);
		}
		// Else fall back to superclass implementation: calling loadView.
		return super.createView(viewName, locale);   // ===>If not, create, which is the normal operation!
	}

Go to the last create

	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");

		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
		view.setUrl(getPrefix() + viewName + getSuffix());  // Key points! Take the prefix + name + suffix you set as the template path!

		// Various attributes of the set in the following pile,
		// Leave him alone! After setting, return to the bottom
		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}

		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());

		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
			view.setExposePathVariables(exposePathVariables);
		}
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}

Next is render

@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isTraceEnabled()) {
			logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
				" and static attributes " + this.staticAttributes);
		}
		//Sort out and merge the data of the model and collect it into a map,
		// Breakpoint view the value of mergedModel and find the data defined in the controller~~~
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response); //Set up two header s, regardless of him
		//Implementation, the focus is here! [key points]
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); // ===>Get in!
	}

The last step is to forward it to the servlet

@Override
	protected void renderMergedOutputModel(
			// Here, all relevant information is collected into a Map
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
		//Set the response data into the attribute of the request object, and the data is handed over to the servlet built-in container
		// debug check the data of the model and try it! We will find the data we have bound~~~
		exposeModelAsRequestAttributes(model, request);  // ===>[key point: data binding]
		// Expose helpers as request attributes, if any.
		exposeHelpers(request);  // ===>Just set two header s
		//Get the jump address and preprocessing path
		// Judge whether to jump to path dead cycle
		String dispatcherPath = prepareForRendering(request, response);  // ===>

		//servlet api to get the forwarding path.
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(request, response)) {
			response.setContentType(getContentType());  // Content type setting
			if (logger.isDebugEnabled()) {
				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.include(request, response);
		} else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			//Forward forward!
			// The next steps are left to the servlet container. jsp parsing and returning to the foreground browser.
			rd.forward(request, response);
		}
	}

summary

  1. After receiving the request, tomcat sends it to DispatcherServlet
  2. After receiving the request, the dispatcher servlet queries the corresponding url address in the handle to find the one with the highest matching degree
  3. It is assembled into an execution link (specifically executed services and interceptors) and returned to the dispatcher servlet
  4. The processor mapper executes the pre method in the interceptor - > the processor reflection calls the service controller to process the service - > the processor mapper executes the postHandle in the interceptor and returns the result to the dispatcher servlet
  5. DispatcherServlet is handed over to the view parser for path splicing and data binding, setting response information and some checks (no jump path loop)
  6. Finally, forward is forwarded to tomcat, and the response is sent to the client through Tomcat

In the source code, view parsing and rendering are done together, so we write one here.

Topics: Java Spring MVC mvc