Spring source code analysis spring MVC


   ContextLoaderListener is located in Spring Web and is mainly used to provide Spring core functions (such as IOC) for other Web frameworks (such as Structs framework); DispatcherServlet is located in Spring webmvc and has the core functions of Spring and Web functions. DispatcherServlet can also be integrated with ContextLoaderListener (taking the ApplicationContext of ContextLoaderListener as the parent ApplicationContext of the ApplicationContext of DispatcherServlet), However, individuals prefer to use DispatcherServlet alone (many holes may be buried due to the inheritance of ApplicationContext during operation).

ContextLoaderListener

// ContextLoaderListener is used to create a globally discoverable ApplicationContext when the Servlet container starts
// ContextLoaderListener inherits from ContextLoader. It is the parent class that creates ApplicationContext and publishes it to the global (saved as the property of ServletContext)
// In order to execute the program when the Servlet container starts, ContextLoaderListener implements the ServletContextListener interface
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
}

// Responsible for creating an ApplicationContext
public class ContextLoader {
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(...);
		}

		long startTime = System.currentTimeMillis();

		if (this.context == null) {
			// First pass determineContextClass(...) Get the specific implementation class of ApplicationContext, and then create an instance through this class
			this.context = createWebApplicationContext(servletContext);
		}
		
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				if (cwac.getParent() == null) {
					// loadParentContext(...) null is returned by default and can be overridden by subclasses (it is not overridden in practical applications)
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				// Configure and refresh context
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
			
		// Save the created context in ServletContext so that other parts of the Web application can obtain this context through ServletContext
		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;
	}

	// Determine the specific implementation class of ApplicationContext
	protected Class<?> determineContextClass(ServletContext servletContext) {
		// Get the parameter whose key is "contextClass" in ServletContext as the specific implementation class of ApplicationContext
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
		} else {
			// defaultStrategies is org / springframework / Web / context / contextloader under the spring web package Properties loaded from the properties file (this loading process is completed in the static block of this class)
			// org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
			// Therefore, the specific implementation class of ApplicationContext is XmlWebApplicationContext
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
		}
	}

	// Configure and refresh context
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		// The id attribute of AbstractApplicationContext defaults to objectutils identityToString(this)
		// If the id is still the default id, reset the id
		// First, set the id through the parameter value whose key is "contextConfigLocation" in ServletContext
		// If the ServletContext does not contain the key, use org springframework. web. context. Webapplicationcontext: path of ServletContext
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			} else {
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		// Save ServletContext in ApplicationContext for later use
		wac.setServletContext(sc);

		// Get the parameter whose key is "contextConfigLocation" in ServletContext as the path of configured XML
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
		
		// The default is standardservlet environment
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}

		// Execute applicationcontextinitializer before refresh() initialize(...), It is usually used to customize the initialization of ApplicationContext
		customizeContext(sc, wac);
		
		// Refresh ApplicationContext
		wac.refresh();
	}

	// Execute initialize(...) of all registered applicationcontextinitializers method
	protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
		// Decide what types of classes are available
		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc);

		// Instantiate and register the determined ApplicationContextInitializer class
		for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
			// Get the Class represented by the type parameter t of applicationcontextinitializer < T >
			Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
			if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
				throw new ApplicationContextException(...);
			}
			this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
		}

		// Sort all registered applicationcontextinitializers and call their initialize(...) in turn method
		AnnotationAwareOrderComparator.sort(this.contextInitializers);
		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
			initializer.initialize(wac);
		}
	}

	// Decide which applicationcontextinitializers need to be registered
	protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
			determineContextInitializerClasses(ServletContext servletContext) {
		// The ApplicationContextInitializer type used to hold all registrations
		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = new ArrayList<>();

		// The full name of the subclass of ApplicationContextInitializer configured with the key of "globalInitializerClasses" in the ServletContext
		// The full names of multiple classes are ',', ';' Or '\ t\n' split
		String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
		if (globalClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
				// loadInitializerClass(...) Load the Class object according to the full name of the Class, which must be a subclass of ApplicationContextInitializer
				classes.add(loadInitializerClass(className));
			}
		}

		// The parameter 'initializer' in the 'initializercontext' subclass is the full name of the initializer
		// The full names of multiple classes are ',', ';' Or '\ t\n' split
		String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
		if (localClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
				classes.add(loadInitializerClass(className));
			}
		}

		return classes;
	}
}

DispatcherServlet

   dispatcher servlet (WEB function) - > frameworkservlet (container function) - > httpservlet bean (configuration function) - > httpservlet - > genericservlet - > Servlet

initialization

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
	// Servlet initialization entry
	public final void init() throws ServletException {
		// Encapsulate all initparameters configured by Servlet into PropertyValues, and all key s in requiredProperties (a string set) must have corresponding initparameters
		// Set the properties in the current Servlet object (actually DispatcherServlet object) with pvs, that is, use InitParameter to set the properties of DispatcherServlet
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}

		// Initialize ApplicationContext and Servlet
		initServletBean();
	}
}

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
	protected final void initServletBean() throws ServletException {
		long startTime = System.currentTimeMillis();

		// Initialize WebApplicationContext
		this.webApplicationContext = initWebApplicationContext();

		// The default is empty method, which can be extended by subclasses. In fact, the method is not extended to DispatcherServlet
		initFrameworkServlet();
	}

	protected WebApplicationContext initWebApplicationContext() {
		// Get ApplicationContext from ServletContext (ContextLoaderListener sets ApplicationContext in ServletContext)
		// Actually call ServletContext getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)
		WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		// If the webApplicationContext associated with the current Servlet is not empty, set its parent ApplicationContext and configure and refresh it
		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) {
			// Actually call ServletContext Getattribute (attributename) get ApplicationContext
			// attributeName is defined by frameworkservlet Contextattribute attribute attribute (null by default)
			wac = findWebApplicationContext();
		}
		
		// Usually, DispatcherServlet will enter this branch when starting, and create ApplicationContext here
		if (wac == null) {
			// Create an ApplicationContext and use rootContext as the parent ApplicationContext
			wac = createWebApplicationContext(rootContext);
		}

		// onRefresh(...) Implemented in dispatcher servlet, it is responsible for completing the initialization of Spring's Web functions
		// this.refreshEventReceived indicates whether the initialization of Spring's Web function has been completed
		if (!this.refreshEventReceived) {
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		// this.publishContext is true by default and publishes the ApplicationContext so that other places can get the ApplicationContext through ServletContext
		if (this.publishContext) {
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

	// Create WebApplicationContext
	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		// Get the ApplicationContext type that needs to be instantiated and instantiate it
		// The default ApplicationContext type is frameworkservlet DEFAULT_ CONTEXT_ CLASS = XmlWebApplicationContext. class
		Class<?> contextClass = getContextClass();
		ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);

		// Set the location of the XML configuration file of WebApplicationContext. Its value comes from the contextConfigLocation property. The property can be configured through the InitParameter configured by the Servlet
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}

		// Configure and refresh context
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
	
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		// Set the id of ApplicationContext, which is similar to the configureAndRefreshWebApplicationContext method of ContextLoader
		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());
		
		// Register an ApplicationListener and call WAC The listening event will be published when refresh(), and the sourcefilteringlistener will be called back at that time onApplicationEvent(...) method
		// SourceFilteringListener.onApplicationEvent(...) Onapplicationevent (...) of the second construction parameter (ContextRefreshListener) is called directly method
		// ContextRefreshListener.onApplicationEvent(...) The onRefresh(...) of the Servlet implementation class is called directly method
		// onRefresh(...) of DispatcherServlet Method completes the initialization of Spring's Web function
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		// The default is standardservlet environment
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		// The default is null function, which is provided for subclass extension (in fact, it is not extended to DispatcherServlet)
		postProcessWebApplicationContext(wac);

		// Execute applicationcontextinitializer before refresh() initialize(...), It is usually used to customize the initialization of ApplicationContext
		// Customizecontextloader (...) Similar method
		applyInitializers(wac);

		// Refresh context
		wac.refresh();
	}

	// Execute initialize(...) of all registered applicationcontextinitializers method
	protected void applyInitializers(ConfigurableApplicationContext wac) {
		// The full name of the subclass of ApplicationContextInitializer configured with the key of "globalInitializerClasses" in the ServletContext
		// The full names of multiple classes are ',', ';' Or '\ t\n' split
		String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
		if (globalClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
				// loadInitializer(...) Instantiate according to the full name of the class
				this.contextInitializers.add(loadInitializer(className, wac));
			}
		}

		// The full name of the subclass of ApplicationContextInitializer represented by the contextInitializerClasses member variable, and the attribute can be configured through the InitParameter configured by Servlet
		// The full names of multiple classes are ',', ';' Or '\ t\n' split
		if (this.contextInitializerClasses != null) {
			for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
				this.contextInitializers.add(loadInitializer(className, wac));
			}
		}

		// Sort all registered applicationcontextinitializers and call their initialize(...) in turn method
		AnnotationAwareOrderComparator.sort(this.contextInitializers);
		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
			initializer.initialize(wac);
		}
	}
}

// This class mainly completes the Web function of Spring
public class DispatcherServlet extends FrameworkServlet {
	// Initialization of Spring's Web function
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
	
	// Initialize nine components of Spirng MVC  
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}	
}

Nine components

   the initialization of Spring MVC major 9 components basically uses the same routine. Except that the file upload component is null by default, other components finally pass through dispatcherservlet Getdefaultstrategies (...) to obtain the default value (configured through DispatcherServlet.properties in org/springframework/web/servlet directory under Spring webmvc package. Different Spring versions can expand component functions through this configuration). You can override the default components by registering bean s in the appapplicationcontext.

public class DispatcherServlet extends FrameworkServlet {
	// In fact, it is to call getDefaultStrategies(...) Method, but converts the list to an object of a single element, that is, the list must have one and only one element
	protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
		List<T> strategies = getDefaultStrategies(context, strategyInterface);
		if (strategies.size() != 1) {
			throw new BeanInitializationException(...);
		}
		return strategies.get(0);
	}

	// Get dispatcherservlet.com in org/springframework/web/servlet directory under spring webmvc package In the properties property file,
	// key is the instantiated list of value corresponding to the full class name of strategyInterface (value is the full class name of the implementation class of strategyInterface, and multiple full class names are separated by ','
	// If dispatcherservlet If there is no corresponding key in properties, an empty list will be returned
	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		// The defaultStrategies of the Properties object will be initialized in the static block of the class,
		// Dispatcherservlet.com in org/springframework/web/servlet directory under spring webmvc package The properties file is loaded
		// If the following key value is saved in this file
		// org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
		// org.springframework.web.servlet.HandlerMapping=org...BeanNameUrlHandlerMapping,org...RequestMappingHandlerMapping
		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		
		if (value == null) {
			// If the implementation class of the corresponding strategyInterface is not configured, an empty list is returned
			return new LinkedList<>();
		} else {
			// Resolve the full names of multiple classes divided by ',' to array format
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<>(classNames.length);
			// Instantiate the configured class, put it into the list and return
			for (String className : classNames) {
				Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
				// Build a rootbean definition of prototype pattern according to clazz
				// And call createBean(...) of BeanFactory in ApplicationContext Method to create an object, which will execute the extension of creating a Bean (including attribute injection, etc.)
				Object strategy = createDefaultStrategy(context, clazz);
				strategies.add((T) strategy);
			}
			return strategies;
		} 
	}	
}	

MultipartResolver

  this component is used to convert the request object of uploading files into MultipartHttpServletRequest type, so as to realize the file upload function. By default, this component is not registered. You can register a bean named "multipartResolver" with the container to register the component.

  the implementation classes provided in spring MVC are:

  • CommonsMultipartResolver: this class depends on the Commons fileUpload package, so you need to import it
  • StandardServletMultipartResolver:
public interface MultipartResolver {
	// Judge whether the request is a file upload request (usually judge whether the ContextType of the request starts with "multipart /")
	boolean isMultipart(HttpServletRequest request);
	// Encapsulate the file upload request into MultipartHttpServletRequest, which provides many interfaces for obtaining the uploaded file
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
	// Clean up after file upload
	void cleanupMultipart(MultipartHttpServletRequest request);
}

public class DispatcherServlet extends FrameworkServlet {
	// Get the Bean named "MultipartResolver" from the ApplicationContext as the MultipartResolver of the Web application, which is null by default
	private void initMultipartResolver(ApplicationContext context) {
		try {
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		} catch (NoSuchBeanDefinitionException ex) {
			this.multipartResolver = null;
		}
	}
}	

LocaleResolver

   this component is mainly used for internationalization. The Locale object is obtained by parsing the request. AcceptHeaderLocaleResolver is used by default (this is related to the version of spring MVC, and the following default values are also related to the version, which will not be described below). You can register a bean named "localeResolver" with the container to override the default component.

  the implementation classes provided in spring MVC are:

  • FixedLocaleResolver: get the default Locale (you can set the default Locale through setDefaultLocale(...). If the default Locale is not set, get the Locale getDefault()
  • AcceptHeaderLocaleResolver: get Locale from the request, i.e. request Getlocale() (in fact, Locale is generated according to the value of "accept language" in the request header), which cannot be passed through request Getlocale() gets the default Locale (you can set the default Locale through setDefaultLocale(...))
  • SessionLocaleResolver: get the Locale from the session (you can set the Locale of the session scope through setLocale(...)). If you can't get the Locale from the session, get the default Locale (you can set the default Locale through setDefaultLocale(...)). If you still can't get the Locale, go through request Getlocale() get.
  • Cookie localeresolver: get the Locale from the cookie (you can set the Locale of the cookie scope through setLocale(...))
public interface LocaleResolver {
	// Parse the request to get the Locale object
	Locale resolveLocale(HttpServletRequest request);
	
	void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);
}

public class DispatcherServlet extends FrameworkServlet {
	// Get the Bean named "LocaleResolver" from the ApplicationContext as the LocaleResolver of the Web application
	private void initLocaleResolver(ApplicationContext context) {
		try {
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
		} catch (NoSuchBeanDefinitionException ex) {
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
		}
	}
}

ThemeResolver

   this component is mainly used for topic function. The topic name is obtained by parsing the request. FixedThemeResolver is used by default. You can register a bean named "themeResolver" with the container to override the default component.

  the implementation classes provided in spring MVC are:

  • FixedThemeResolver: get the default topic name. The initial value of the default topic name is "theme", which can be reset by setDefaultThemeName(...)
  • Cookie themeresolver: get the subject name from the cookie. You can set the subject name of the scope of the cookie through setThemeName(...). If you can't get the subject name from the cookie, get the default subject name (the initial value and setting method of the default subject name are the same as fixedthemresolver).
  • SessionThemeResolver: get the topic name from the session. You can set the topic name of the session scope through setThemeName(...). If you can't get the topic name from the session, get the default topic name (the initial value and setting method of the default topic name are the same as FixedThemeResolver).
public interface ThemeResolver {
	// Resolve the request to get the subject name
	String resolveThemeName(HttpServletRequest request);
	
	void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName);
}

public class DispatcherServlet extends FrameworkServlet {
	// Get the Bean named "ThemeResolver" from the ApplicationContext as the ThemeResolver of the Web application
	private void initThemeResolver(ApplicationContext context) {
		try {
			this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
		} catch (NoSuchBeanDefinitionException ex) {
			this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
		}
	}
}

HandlerMapping

   the component will obtain the matching interceptor and the object to process the request according to the request (generally the url of the request) (how the object processes the request is determined by the HandlerAdapter). By default, BeanNameUrlHandlerMapping, RequestMappingHandlerMapping and RouterFunctionMapping are used. You can register one or more beans of the HandlerMapping implementation class with the container to override the default component. When the client initiates a request, the getHandler(...) methods of these HandlerMapping will be called in turn until the HandlerExecutionChain is obtained.

  the implementation classes provided in spring MVC are:

  • BeanNameUrlHandlerMapping: when the request arrives, match the url according to the bean name of the Controller (the Controller must be an interface that implements the Controller and has been registered in the container), and call the handleRequest(...) method implemented by the Controller
  • RequestMappingHandlerMapping: implements the InitializingBean interface and completes the initialization in its afterpropertieset (...) method. Match the url according to the RequestMapping annotation of the Controller and its methods (the Controller must be marked by @ Controller or @ RequestMapping, and the bean has been registered in the container). The current MVC programming is almost all based on the HandlerMapping, and the following is only the source code analysis of this class
  • RouterFunctionMapping
  • SimpleUrlHandlerMapping
public interface HandlerMapping {
	// Obtain the execution chain according to the request (multiple interceptors can be included, and one Handler must be included)
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

public class DispatcherServlet extends FrameworkServlet {
	// Get all handlerMapping implementation classes or beans named "handlerMapping" from ApplicationContext as the list < handlerMapping > of Web applications
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		// this.detectAllHandlerMappings defaults to true
		if (this.detectAllHandlerMappings) {
			// Get the beans of all HandlerMapping implementation classes from the ApplicationContext and sort them as the list < HandlerMapping > of the Web application
			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 {
				// Get the Bean named "handlerMapping" from the ApplicationContext as the list < handlerMapping > of the Web application
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			} catch (NoSuchBeanDefinitionException ex) {
				// Do nothing
			}
		}

		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		}
	}
}

initialization

    almost all HandlerMapping inherits AbstractHandlerMapping, which implements the ApplicationContextAware interface, so the setApplicationContext(...) method is the entry for initialization.
  more detailed inheritance relationship: abstracthandlermapping - > webapplicationobjectsupport - > applicationobjectsupport

public abstract class ApplicationObjectSupport implements ApplicationContextAware {
	public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
		if (context == null && !isContextRequired()) { // context is usually not null, so it will not enter the branch
			this.applicationContext = null;
			this.messageSourceAccessor = null;
		} else if (this.applicationContext == null) { // Usually enter this branch
			// Judge whether the ApplicationContext is the expected type. If not, throw an exception
			if (!requiredContextClass().isInstance(context)) {
				throw new ApplicationContextException(...);
			}
			this.applicationContext = context;
			this.messageSourceAccessor = new MessageSourceAccessor(context);
			// Initialization is implemented by subclasses, which are actually implemented in WebApplicationObjectSupport
			initApplicationContext(context);
		} else { // Avoid repeated initialization
			if (this.applicationContext != context) {
				throw new ApplicationContextException(...);
			}
		}
	}	
}

public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport {
	protected void initApplicationContext(ApplicationContext context) {
		// Call initApplicationContext(context) of parent ApplicationObjectSupport
		// The method of the parent class directly calls the initApplicationContext() method, which is implemented by AbstractHandlerMapping
		super.initApplicationContext(context);
		// If it is a Web application, the initialization of Servlet is related. Personally, I think the registration of Handler is realized by the subclass initServletContext(...) The method is more appropriate
		// However, the method is empty by default in spring MVC
		if (this.servletContext == null && context instanceof WebApplicationContext) {
			this.servletContext = ((WebApplicationContext) context).getServletContext();
			if (this.servletContext != null) {
				initServletContext(this.servletContext);
			}
		}
	}
}	

public abstract class AbstractHandlerMapping {
	protected void initApplicationContext() throws BeansException {
		// It's actually an empty method and does nothing
		extendInterceptors(this.interceptors);
		// Register all beans of MappedInterceptor type into adaptedInterceptors
		detectMappedInterceptors(this.adaptedInterceptors);
		// Put this Interceptors in the interceptors variable are also registered with adaptedInterceptors
		initInterceptors();
	}
}
BeanNameUrlHandlerMapping

   BeanNameUrlHandlerMapping inherits AbstractMethodMapping, realizes the initialization of public functions, and rewrites the extension interface provided in the initialization process to realize the initialization of Handler.
  BeanNameUrlHandlerMapping -> AbstractDetectingUrlHandlerMapping -> AbstractUrlHandlerMapping

public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {
	public void initApplicationContext() throws ApplicationContextException {
		// Ensure that the function of the parent class can be realized (initialize the interceptor)
		super.initApplicationContext();
		// Initialize Handler
		detectHandlers();
	}

	protected void detectHandlers() throws BeansException {
		ApplicationContext applicationContext = obtainApplicationContext();
		// this.detectHandlersInAncestorContexts is false by default, so the beanName in the ancestor context will not be obtained
		String[] beanNames = (this.detectHandlersInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
				applicationContext.getBeanNamesForType(Object.class));

		for (String beanName : beanNames) {
			// The url is determined according to beanName, which is implemented by the subclass BeanNameUrlHandlerMapping
			String[] urls = determineUrlsForHandler(beanName);
			if (!ObjectUtils.isEmpty(urls)) {
				// Bind all URLs to corresponding bean s respectively
				registerHandler(urls, beanName);
			}
		}
	}
}	

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
	// Take the ID of the bean corresponding to beanName starting with "/" and all aliases as urls
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>();
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = obtainApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}
}
RequestMappingHandlerMapping

  RequestMappingHandlerMapping not only inherits AbstractMethodMapping and realizes the initialization of public functions, but also completes the initialization of Handler by implementing the InitializingBean interface.
  RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMapping

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping {
	public void afterPropertiesSet() {
		this.config = new RequestMappingInfo.BuilderConfiguration();
		// UrlPathHelper is mainly used to encode and decode URLs and obtain URLs from requests
		this.config.setUrlPathHelper(getUrlPathHelper());
		// It is used to match the request and obtain the parameters in the url path (such as param in / ab/{param})
		this.config.setPathMatcher(getPathMatcher());
		this.config.setSuffixPatternMatch(useSuffixPatternMatch());
		this.config.setTrailingSlashMatch(useTrailingSlashMatch());
		this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
		this.config.setContentNegotiationManager(getContentNegotiationManager());

		// This method is implemented in AbstractHandlerMethodMapping to complete the registration of the handler
		super.afterPropertiesSet();
	}

	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		// Get the @ RequestMapping annotation of the method and initialize RequestMappingInfo according to various properties of the @ RequestMapping annotation
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			// Get @ RequestMapping annotation on class
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				// Merge the two annotations
				info = typeInfo.combine(info);
			}
			// Add default prefix
			String prefix = getPathPrefix(handlerType);
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
			}
		}
		return info;
	}

	// Initialize cross domain
	protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		Class<?> beanType = handlerMethod.getBeanType();
		CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
		CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);

		// If neither class nor method is marked by the CrossOrigin annotation, cross domain requests are not supported
		if (typeAnnotation == null && methodAnnotation == null) {
			return null;
		}

		// Update the configuration with the CrossOrigin annotation on the class, and then update the configuration with the CrossOrigin annotation on the method (aggregation types will be merged)
		CorsConfiguration config = new CorsConfiguration();
		updateCorsConfig(config, typeAnnotation);
		updateCorsConfig(config, methodAnnotation);

		// If the method of allowing is null, update it with the method of allowing
		if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
			for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
				config.addAllowedMethod(allowedMethod.name());
			}
		}
		
		// Configure defaults
		return config.applyPermitDefaultValues();
	}
}	

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	// Just as a hook for callback, the specific work is transferred to initHandlerMethods()
	public void afterPropertiesSet() {
		initHandlerMethods();
	}

	protected void initHandlerMethods() {
		// getCandidateBeanNames() is used to get the ID s of all beans registered in ApplicationContext
		// this. Detecthandlermethods inancessorcontexts determines whether to include the bean s registered in the component ApplicationContext. The default value is false, that is, not included
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				// Register Handler, which is actually a method
				processCandidateBean(beanName);
			}
		}
		
		// The post-processing of handler registration is implemented by subclasses. In fact, it only prints some logs and does not do other meaningful things
		handlerMethodsInitialized(getHandlerMethods());
	}

	protected void processCandidateBean(String beanName) {
		Class<?> beanType = obtainApplicationContext().getType(beanName);
		// Judge whether the current bean is a Controller according to whether the class (or its parent class, and the original class of the agent) is marked by @ Controller or @ RequestMapping (or the annotation marked by it)
		// Only the Controller method can become a Handler
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}

	protected void detectHandlerMethods(Object handler) {
		// If it is a String type, it represents beanName. The bean type is obtained through ApplicationContext
		// If it is not a String type, get its type directly
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			// If the proxy class is used, the original type is obtained here
			Class<?> userType = ClassUtils.getUserClass(handlerType);

			// Get the mapping of Method and its configuration (RequestMapping configuration). T here represents the type of RequestMappingInfo (including mapping configuration, matching Method with url, etc.)
			// When the request arrives, it will be matched to the corresponding mapping configuration by matching Method according to the url of the request, and then find the corresponding Method (i.e. Handler) according to the mapping configuration
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					return getMappingForMethod(method, userType);
				});
			methods.forEach((method, mapping) -> {
				// Convert method to AOP proxy method
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				// Directly delegate to the Mapping registry for registration. The registry is an instance of the internal class MappingRegistry
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

	class MappingRegistry { 
		public void register(T mapping, Object handler, Method method) {
			// Kotlin language, ignore
			if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
				...
			}
			
			this.readWriteLock.writeLock().lock();
			try {
				// Encapsulate the handler and method as the final handler. For RequestMappingHandlerMapping, the final handler is a HandlerMethod type
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				// Verify the handlerMethod, mainly to avoid repeated registration
				validateMethodMapping(handlerMethod, mapping);
				// Register the relationship between RequestMappingInfo and Handler. A RequestMappingInfo may contain multiple URLs
				this.mappingLookup.put(mapping, handlerMethod);
				// URLs include direct URLs and regular URLs. Regular URLs need time-consuming parsing, while ordinary URLs only need string comparison. Therefore, direct URLs are processed separately to improve matching efficiency
				// If the url is a regular cache, then each url is not a separate cache
				// Direct URLs only require simple string comparison without regular matching, so that mapping (request mapping info) can be found quickly
				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					this.urlLookup.add(url, mapping);
				}

				// mapping name
				String name = null;
				if (getNamingStrategy() != null) {
					// By default, the simple class name of Controller is used, and the initial letters of each word are capitalized + "#" + method name
					// Such as com test. Mycontroller's test() method, then the name is MC#test()
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				// Get cross domain configuration, initCorsConfiguration(...) Implemented in RequestMappingHandlerMapping
				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					// Register the cross domain configuration corresponding to the handler
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				// Registering the relationship between mapping and MappingRegistration
				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			} finally {
				this.readWriteLock.writeLock().unlock();
			}
		}
	}	
}	

public final class MethodIntrospector {
	public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
		final Map<Method, T> methodMap = new LinkedHashMap<>();
		// After initialization, it will include the target class and all interfaces implemented by it (including all interfaces implemented by its ancestor class). If the class is a proxy, it will also include the original class being proxied
		Set<Class<?>> handlerTypes = new LinkedHashSet<>();
		Class<?> specificHandlerType = null;

		// If it is an agent class (here, it refers to the agent class of the Controller), obtain the original class (i.e. the Controller itself) and register it
		if (!Proxy.isProxyClass(targetType)) {
			specificHandlerType = ClassUtils.getUserClass(targetType);
			handlerTypes.add(specificHandlerType);
		}
		// Register the target class and all interfaces it implements (including all interfaces implemented by its ancestor class)
		handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));

		// Scan all methods defined in the target class, the original class, and all interfaces implemented by them (private methods, etc. will be scanned)
		for (Class<?> currentHandlerType : handlerTypes) {
			// The original class being represented
			final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);

			// The first parameter is the target class, the second parameter is used to register callbacks, and the third parameter is used to filter callbacks (bridge methods and composite methods are filtered out by default, and the methods defined in the parent class are not included)
			ReflectionUtils.doWithMethods(currentHandlerType, method -> {
				// If it is a special method (such as the method of the interface), convert it to the method of the target class
				Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
				
				// Metadata lookup is a lambda expression passed in. In fact, it simply calls back requestmappinghandlermapping getMappingForMethod(...)
				T result = metadataLookup.inspect(specificMethod);
				if (result != null) {
					Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
					if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
						methodMap.put(specificMethod, result);
					}
				}
			}, ReflectionUtils.USER_DECLARED_METHODS);
		}

		return methodMap;
	}

	public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
		// From here, we can see that the methods defined by the current class (whether private, protected or public) can be registered
		Method[] methods = getDeclaredMethods(clazz, false);
		for (Method method : methods) {
			// Filtering off bridge method and synthesis method
			if (mf != null && !mf.matches(method))  continue;
			
			// Callback for method registration
			mc.doWith(method);
		}
		
		// Because the mf passed in is USER_DECLARED_METHODS, so the methods defined in the parent class will not be scanned
		if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {
			// Processing the methods defined in the parent class will recurse all ancestor classes
			doWithMethods(clazz.getSuperclass(), mc, mf);
		} else if (clazz.isInterface()) {
			// Because it will not enter the previous branch, the interface methods inherited by the interface will be processed
			// If you enter the previous branch, it indicates that the methods of the target class and its ancestor classes have been processed, then the methods of all interfaces must also be processed (any method of the interface must be implemented by a class)
			for (Class<?> superIfc : clazz.getInterfaces()) {
				doWithMethods(superIfc, mc, mf);
			}
		}
	}
}

Get HandlerExecutionChain

// Almost all HandlerMapping inherits from this class
public abstract class AbstractHandlerMapping {
	// This is a template method. Subclasses rewrite some interfaces according to their own needs
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		// Get the handler according to the request, such as url
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			// The default handler when a matching handler cannot be found. It is initialized to null. You need to set the default handler to get it
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		
		// If the handler is of type String, then the handler is actually the beanName registered by the handler
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		// Get the processing chain, including a handler and a series of interceptors
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		// hasCorsConfigurationSource(...) Judge whether cross domain is supported according to whether the handler implements the CorsConfigurationSource interface
		// The handler of RequestMethodHandleMapping is a HandlerMethod class. It is impossible to implement the CorsConfigurationSource interface
		// The handler of BeanNameUrlHandlerMapping is the Controller class. It depends on whether the programmer implements the CorsConfigurationSource interface
		// CorsUtils.isPreFlightRequest(...) Judge whether cross domain is supported according to the request information
		// The request must contain the request header Origin and access control request method, and must be an OPTIONS type request, so it is cross domain
		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
			// If the handler implements the CorsConfigurationSource interface, use its getCorsConfiguration(...) Method to get CorsConfiguration
			// RequestMethodHandleMapping overrides this method. First call this method of the parent class. If CorsConfiguration cannot be obtained, then search the method registered during initialization
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			// Merge the two CorsConfiguration properties
			config = (config != null ? config.combine(handlerConfig) : handlerConfig);
			// Add an interceptor at the beginning of the interceptor chain. The interceptor will set the response header of response according to the configuration
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}
	
	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		// If the incoming handler is already a HandlerExecutionChain, then it already contains the handler and does not need to be processed
		// Otherwise, we will create a new HandlerExecutionChain and initialize it with handler
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		// Get relative url from request
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			// MappedInterceptor interceptors need to match URLs to intercept. Interceptors of other types intercept all requests
			// Interceptors configured through < MVC: interceptors / > will be resolved to MappedInterceptor interceptors
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			} else {
				chain.addInterceptor(interceptor);
			}
		}
		
		return chain;
	}
}	
BeanNameUrlHandlerMapping
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		// Get relative url from request
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		request.setAttribute(LOOKUP_PATH, lookupPath);
		
		// Find handler
		Object handler = lookupHandler(lookupPath, request);
		if (handler == null) {
			Object rawHandler = null;
			// If the path is just a "/", the rootHandler will be used first. The rootHandler is null by default
			if ("/".equals(lookupPath)) {
				rawHandler = getRootHandler();
			}
			// Otherwise, the default defaultHandler is used for processing, and the default defaultHandler is null
			if (rawHandler == null) {
				rawHandler = getDefaultHandler();
			}
			// Verify and publish parameters. If you enter this branch, the HandlerMapping has a default Handler, and the subsequent HandlerMapping cannot work
			if (rawHandler != null) {
				if (rawHandler instanceof String) {
					String handlerName = (String) rawHandler;
					rawHandler = obtainApplicationContext().getBean(handlerName);
				}
				validateHandler(rawHandler, request);
				handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
			}
		}
		return handler;
	}

	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		// Get the Handler from the handlerMap according to the key (relative url)
		Object handler = this.handlerMap.get(urlPath);
		if (handler != null) {
			// If it is a String type, it means that you get the beanName. You can get the Bean as the handler through appapplicationcontext
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			// Verification is actually an empty function
			validateHandler(handler, request);
			// Publish some parameters
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}

		// Unable to match directly, regular matching is adopted, and matching patterns saves the url in regular format on the matching
		List<String> matchingPatterns = new ArrayList<>();
		for (String registeredPattern : this.handlerMap.keySet()) {
			if (getPathMatcher().match(registeredPattern, urlPath)) {
				matchingPatterns.add(registeredPattern);
			} else if (useTrailingSlashMatch()) {
				if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
					matchingPatterns.add(registeredPattern + "/");
				}
			}
		}

		// Take the smallest one as the regular url of the best match
		String bestMatch = null;
		Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
		if (!matchingPatterns.isEmpty()) {
			matchingPatterns.sort(patternComparator);
			bestMatch = matchingPatterns.get(0);
		}
		
		if (bestMatch != null) {
			// Find the corresponding handler according to the regular url
			handler = this.handlerMap.get(bestMatch);
			if (handler == null) {
				if (bestMatch.endsWith("/")) {
					handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
				}
				if (handler == null) {
					throw new IllegalStateException(...);
				}
			}
			
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			// Get matching '*' and '?' Segment (segment is a segment separated by "/" in the path)
			String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);

			// It is used to save the matching parameters and their values in the regular url
			Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
			for (String matchingPattern : matchingPatterns) {
				if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
					Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
					Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
					uriTemplateVariables.putAll(decodedVars);
				}
			}
			// Publish some parameters
			return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
		}

		return null;
	}

	// Used to publish some request related parameters
	protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
			String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
		// Create a HandlerExecutionChain with rawHanlder as the handler to register the default interceptor
		HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
		// It mainly publishes the best matching path (such as / api/goods/{id}) and handler (in the form of request attribute)
		chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
		if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
			// It mainly publishes the parameter values in the path to a request attribute of Map type through key value
			chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
		}
		return chain;
	}
}	
RequestMappingHandlerMapping
public AbstractHandlerMethodMapping<RequestMappingInfo> extends AbstractHandlerMapping {
	// Get Handler on request
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		// Get relative url from request
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		request.setAttribute(LOOKUP_PATH, lookupPath);
		this.mappingRegistry.acquireReadLock();
		try {
			// Find Handler Based on url
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			// handlerMethod.createWithResolvedBean() ensures that the object used to call the Method is the expected object instance (Controller instance)
			// If it is a String type, take this as the beanName and obtain the Bean from the ApplicationContext as the expected object instance
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		} finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		// Match contains a mapping and a Handler, that is, as long as it matches, a match will be generated and saved here (the mapping of match only corresponds to one url)
		List<Match> matches = new ArrayList<>();
		// Obtain the mapping that can be directly matched (RequestMappingInfo of irregular url), which is the original mapping corresponding to the url
		// If the mapping contains multiple URLs during initialization, the mapping at this time also contains multiple URLs
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		// If a direct matching mapping is found, the
		if (directPathMatches != null) {
			// Traverse the directly matched mapping, find the corresponding Handler, and encapsulate the original mapping into a new mapping (the url of the mapping is only the url of the current request)
			addMatchingMappings(directPathMatches, matches, request);
		}
		// If direct matching is not possible, regular matching is adopted (it can be seen that the priority of regular matching is lower than that of direct matching)
		if (matches.isEmpty()) {
			// Traverse all the registered mapping, find the Handler according to the regular matching mapping, and encapsulate a new mapping. The two are combined into a Match
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			Match bestMatch = matches.get(0);
			// If there is more than one Match, find the smallest Match (there cannot be two or more smallest matches)
			if (matches.size() > 1) {
				Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
				matches.sort(comparator);
				bestMatch = matches.get(0);
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(...);
				}
			}
			// Publish handler
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			// Save the parameters and their values in the url path in the form of map and publish them in the form of request attribute
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		} else {
			// If there is no matching handler, get the handler in other ways, and null will be returned by default
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}
}	

HandlerAdapter

  the component will decide how to process the request according to the object that processes the request. HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter, RequestMappingHandlerAdapter and HandlerFunctionAdapter are used by default. You can register one or more bean s of the HandlerAdapter implementation class with the container to override the default component.

  the implementation classes provided in spring MVC are:

  • HttpRequestHandlerAdapter
  • SimpleControllerHandlerAdapter: if the handler is a subclass of the Controller, the adapter is available. It can be used in combination with BeanNameUrlHandlerMapping to directly call the handleRequest(...) method of the Controller to complete the request processing
  • RequestMappingHandlerAdapter: if the handler type is handler, the adapter is available and can be used in combination with RequestMappingHandlerMapping
  • HandlerFunctionAdapter
  • SimpleServletHandlerAdapter
public interface HandlerAdapter {
	// Can the current HandlerAdapter handle the specified handler
	boolean supports(Object handler);
	// Execute the handler to get ModelAndView
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	// Set the last modification time. Generally, it is directly set to - 1
	long getLastModified(HttpServletRequest request, Object handler);
}

public class DispatcherServlet extends FrameworkServlet {
	// Get all handlerAdapter implementation classes or beans named "handlerAdapter" from ApplicationContext as the list < handlerAdapter > of the Web application
	private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		// this.detectAllHandlerAdapters defaults to true
		if (this.detectAllHandlerAdapters) {
			// Get the beans of all HandlerAdapter implementation classes from ApplicationContext and sort them as the list < HandlerAdapter > of Web application
			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 {
				// Get the Bean named "handlerAdapter" from the ApplicationContext as the list < handlerAdapter > of the Web application
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			} catch (NoSuchBeanDefinitionException ex) {
				...
			}
		}
		
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
		}
	}
}

initialization

SimpleControllerHandlerAdapter

  the HandlerAdapter does not need to be initialized.

RequestMappingHandlerAdapter

   like RequestMappingHandlerMapping, RequestMappingHandlerAdapter implements the interfaces of ApplicationContextAware and InitializingBean at the same time. The implementation method and score structure of its ApplicationContextAware interface are exactly the same as those of RequestMappingHandlerMapping. This mainly introduces the initialization of InitializingBean.

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
	public void afterPropertiesSet() {
		// Used to initialize the Bean modified by ControllerAdvice
		initControllerAdviceCache();

		// XxxComposite represents the encapsulation of an aggregate class and exposes the same operations as a single element in the aggregate class
		// Register the default parameter handler, which is used to provide parameter sources for handler and modelAttribute methods
		if (this.argumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		
		// Register the default initBinder method processor, which is used to provide parameter source for initBinder method
		if (this.initBinderArgumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		
		// Register the default return value converter to process the return value of handler
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

	private void initControllerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}

		// Get all Bean instances modified by ControllerAdvice (these beans have been sorted)
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			}
			
			// Cache the method modified by @ ModeAttribute (that is, the global ModeAttribute method)
			Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
			if (!attrMethods.isEmpty()) {
				this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
			}

			// Cache the method modified by @ InitBinder (i.e. global InitBinder method)
			Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
				this.initBinderAdviceCache.put(adviceBean, binderMethods);
			}
			
			// If it is a subclass of RequestBodyAdvice or ResponseBodyAdvice, cache it
			if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
			}
		}

		if (!requestResponseBodyAdviceBeans.isEmpty()) {
			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
		}
	}
}	
HandlerMethodArgumentResolver
  • RequestParamMethodArgumentResolver
  • RequestParamMapMethodArgumentResolver
  • PathVariableMethodArgumentResolver
  • PathVariableMapMethodArgumentResolver
  • MatrixVariableMethodArgumentResolver
  • MatrixVariableMapMethodArgumentResolver
  • ServletModelAttributeMethodProcessor
  • RequestResponseBodyMethodProcessor
  • RequestPartMethodArgumentResolver
  • RequestHeaderMethodArgumentResolver
  • RequestHeaderMapMethodArgumentResolver
  • ServletCookieValueMethodArgumentResolver
  • ExpressionValueMethodArgumentResolver
  • SessionAttributeMethodArgumentResolver
  • RequestAttributeMethodArgumentResolver
  • ServletRequestMethodArgumentResolver
  • ServletResponseMethodArgumentResolver
  • HttpEntityMethodProcessor
  • RedirectAttributesMethodArgumentResolver
  • ModelMethodProcessor
  • MapMethodProcessor
  • ErrorsMethodArgumentResolver
  • SessionStatusMethodArgumentResolver
  • UriComponentsBuilderMethodArgumentResolver
  • RequestParamMethodArgumentResolver
  • ServletModelAttributeMethodProcessor
HandlerMethodReturnValueHandler
  • ModelAndViewMethodReturnValueHandler: handle the HandlerMethod whose return value is ModeAndView and its subclasses
  • ModelMethodProcessor: handle HandlerMethod whose return value is Model and its subclasses
  • ViewMethodReturnValueHandler: handle the HandlerMethod whose return value is View and its subclasses
  • ResponseBodyEmitterReturnValueHandler:
  • StreamingResponseBodyReturnValueHandler
  • HttpEntityMethodProcessor: the return value is the subclass of HttpEntity, but it cannot be the HandlerMethod of RequestHttpEntity and its subclasses
  • HttpHeadersReturnValueHandler
  • CallableMethodReturnValueHandler
  • DeferredResultMethodReturnValueHandler
  • AsyncTaskMethodReturnValueHandler
  • ModelAttributeMethodProcessor
  • RequestResponseBodyMethodProcessor: a controller or method modified by ResponseBody
  • ViewNameMethodReturnValueHandler
  • MapMethodProcessor
  • ModelAndViewResolverMethodReturnValueHandler
  • ModelAttributeMethodProcessor

handler execution

SimpleControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		return ((Controller) handler).handleRequest(request, response);
	}
}
RequestMappingHandlerAdapter
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
	public final boolean supports(Object handler) {
		// supportsInternal(...) implemented by subclass Directly return true
		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}

	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		return handleInternal(request, response, (HandlerMethod) handler);
	}
}	

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
	// Directly return true
	protected boolean supportsInternal(HandlerMethod handlerMethod) {
		return true;
	}

	protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
		ModelAndView mav;
		
		// Check the request. If the check fails, an exception will be thrown. It is mainly used to check whether the request method is supported (such as GET, POST, etc.)
		checkRequest(request);

		// This paragraph is to execute the line mav = invokeHandlerMethod(request, response, handlerMethod)
		// If you want to serialize all requests of the same user, you need to lock the same session (the access of the same session needs to be queued before the request of the same session is released)
		// Serialization access requested by the same user will reduce the throughput of the system, and locking is not required by default
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			} else { 
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		} else {
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

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

		return mav;
	}

	protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
		// Encapsulates requests and responses
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		
		try {
			// Webdatabindfactory contains global and local bindermethods
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			// ModelFactory includes BinderFactory, SessionAttributesHandler, global and local ModeAttributeMethod
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			// invocableMethod includes the executed method, BindFactory, and the conversion method of parameters and return values
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			// It is used to determine the discovery name of the method parameter (for example, the formal parameter of handler should be bound with which argument passed in)
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			// Create a ModeAndView container that can hold some information about how to build a ModeAndView
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			// Add FlashMap mode redirection parameters to the Model container
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			// It mainly obtains the SessionAttribute from the session and executes the Model method to obtain the key value pair, so as to initialize the Model
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			// Asynchronous request related settings
			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			... More asynchronous request settings ...

			// Truly execute the handler method, including method parameter acquisition, etc
			invocableMethod.invokeAndHandle(webRequest, mavContainer);

			// If the asynchronous request is enabled, it is returned directly here without rendering the view. It is directly returned to the Servlet container to release the thread
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			// Building ModelAndView objects
			return getModelAndView(mavContainer, modelFactory, webRequest);
		} finally {
			// Request processing end
			webRequest.requestCompleted();
		}
	}

	private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		// The obtained handlerType is the type of Controller
		Class<?> handlerType = handlerMethod.getBeanType();
		// Get its local BinderMethod according to the Controller type
		Set<Method> methods = this.initBinderCache.get(handlerType);
		if (methods == null) {
			// INIT_BINDER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
			// MethodIntrospector.selectMethods(...) It is used to filter the qualified methods in the class. Here is the method annotated by @ InitBinder
			methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
			// Correspondence between cache Controller and its local BinderMethod
			this.initBinderCache.put(handlerType, methods);
		}
		
		// The binder method is used to save all the global and local requests
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();

		// Register the qualified global BinderMethod with this request
		this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
			// Whether the configured package or the configured subclass of @ handler is under the configured annotation
			if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
				Object bean = controllerAdviceBean.resolveBean();
				for (Method method : methodSet) {
					initBinderMethods.add(createInitBinderMethod(bean, method));
				}
			}
		});

		// Register local BinderMethod with this request
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(createInitBinderMethod(bean, method));
		}
		
		// Building webdatabindfactory with global and local bindermethods
		return createDataBinderFactory(initBinderMethods);
	}

	private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
		// Parse the SessionAttributes annotation of the handler class (i.e. Controller), and encapsulate the parsing result as a SessionAttributesHandler object
		// The handler class and SessionAttributesHandler are cached in the form of key value
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
		
		// The obtained handlerType is the type of Controller
		Class<?> handlerType = handlerMethod.getBeanType();
		// Get its local ModelAttributeMethod according to the Controller type, get it from the cache first, parse it if it can't get it, and then cache it
		Set<Method> methods = this.modelAttributeCache.get(handlerType);
		if (methods == null) {
			// Filter out the methods marked by @ ModelAttribute and not marked by @ RequestMapping in the handlerType class
			methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
			// Correspondence between cache Controller and its local ModeAttributeMethod
			this.modelAttributeCache.put(handlerType, methods);
		}
		
		// It is used to save all global and local modeattributemethods of this request
		List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
		
		// Register the qualified global ModeAttributeMethod with this request
		this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
			// Whether the configured package or the configured subclass of @ handler is under the configured annotation
			if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
				Object bean = controllerAdviceBean.resolveBean();
				for (Method method : methodSet) {
					attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
				}
			}
		});

		// Register local ModeAttributeMethod with this request
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
		}
		
		// Build ModelFactory with BinderFactory, SessionAttributesHandler and global and local ModeAttributeMethod
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}

	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) {
		// Put the Model attribute identified by the @ SessionAttribute annotation into the session
		modelFactory.updateModel(webRequest, mavContainer);

		// If the request has been processed, it does not need ModelAndView to return null directly, such as the method identified by @ RequestBody
		if (mavContainer.isRequestHandled()) {
			return null;
		}
		
		ModelMap model = mavContainer.getModel();
		ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
		if (!mavContainer.isViewReference()) {
			mav.setView((View) mavContainer.getView());
		}

		// If redirection is required, the redirection parameters are temporarily stored, such as FlashAttributes
		if (model instanceof RedirectAttributes) {
			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
			HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
			if (request != null) {
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
		}
		
		return mav;
	}
}

public final class ModelFactory {
	public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) {
		// Get the model from the session and put it into the container
		Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
		container.mergeAttributes(sessionAttributes);
		// Execute the Model method and put the return value into the Model
		invokeModelAttributeMethods(request, container);

		// Ensure that the parameters marked with @ ModelAttribute and supported by sessionAttributes exist in the container
		for (String name : findSessionAttributeArguments(handlerMethod)) {
			if (!container.containsAttribute(name)) {
				Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
				if (value == null) {
					throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
				}
				container.addAttribute(name, value);
			}
		}
	}

	private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) {
		while (!this.modelMethods.isEmpty()) {
			// Use with while to simulate iterative operations
			InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
			
			ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
			if (container.containsAttribute(ann.name())) {
				if (!ann.binding()) {
					container.setBindingDisabled(ann.name());
				}
				continue;
			}

			// Execute the model method. If the return value is not of void type, add the return value to the model
			Object returnValue = modelMethod.invokeForRequest(request, container);
			if (!modelMethod.isVoid()){
				// Determine the key according to the value value of the ModelAttribute annotation and the return value type
				String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
				if (!ann.binding()) {
					container.setBindingDisabled(returnValueName);
				}
				// Add Model data to container
				if (!container.containsAttribute(returnValueName)) {
					container.addAttribute(returnValueName, returnValue);
				}
			}
		}
	}
}	

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// First, get the argument list through getMethodArgumentValues(request, mavContainer, providedArgs)
		// Then call the HandlerMethod method through reflection
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		
		setResponseStatus(webRequest);

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

		// Requests that have not been processed will enter ModelAndView for processing
		mavContainer.setRequestHandled(false);
		
		// Traverse all registered handlermethodreturnvaluehandlers in turn (the registration is completed when the HandlerAdapter is initialized)
		// Until you find a Resolver that supports the current return value and call its handleReturnValue(...) Method, thrown exception not found
		this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}

	// Get the arguments required by the method, which are defined in the parent class InvocableHandlerMethod	
	// providedArgs provides a series of candidate arguments, which are selected first
	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// Gets the formal parameter list of the method
		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			// If no parameters are required, an empty array is returned
			return EMPTY_ARGS;
		}

		// args is used to save arguments, with the same number as formal parameters
		Object[] args = new Object[parameters.length];
		
		// Get the arguments in turn
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			
			// If the supplied candidate argument has subclasses of the current parameter type, the candidate argument is used as the argument of the current parameter
			// Because the general candidate argument is an empty array, that is, there is no candidate argument, so the argument cannot be obtained here
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			
			// Traverse all registered handlermethodargumentresolvers in turn (registration is completed when HandlerAdapter is initialized)
			// Until you find a Resolver that supports the current formal parameter and call its resolveArgument(...) Method, thrown exception not found
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(...);
			}
			args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
		}
		
		return args;
	}
}	

HandlerExceptionResolver

  this component is used to resolve exceptions thrown during request processing. By default, ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver and DefaultHandlerExceptionResolver are used. You can register one or more bean s of the HandlerExceptionResolver implementation class with the container to override the default component.

  the implementation classes provided in spring MVC are:

  • ExceptionHandlerExceptionResolver: define exception handling methods through ControllerAdvice and ExceptionHandler annotations
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver: defines a large number of processing for various exceptions
  • SimpleMappingExceptionResolver
public interface HandlerExceptionResolver {
	ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}

public class DispatcherServlet extends FrameworkServlet {
	// Get all handlerExceptionResolver implementation classes or beans named "handlerExceptionResolver" from ApplicationContext as the list < handlerExceptionResolver > of the Web application
	private void initHandlerExceptionResolvers(ApplicationContext context) {
		this.handlerExceptionResolvers = null;

		// this.detectAllHandlerExceptionResolvers defaults to true
		if (this.detectAllHandlerExceptionResolvers) {
			// Get the beans of all HandlerExceptionResolver implementation classes from the ApplicationContext and sort them as the list < HandlerExceptionResolver > of the Web application
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
			}
		} else {
			try {
				// Get the Bean named "handlerExceptionResolver" from the ApplicationContext as the list < handlerExceptionResolver > of the Web application
				HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
				this.handlerExceptionResolvers = Collections.singletonList(her);
			} catch (NoSuchBeanDefinitionException ex) {
				...
			}
		}

		if (this.handlerExceptionResolvers == null) {
			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
		}
	}	
}

ExceptionHandlerExceptionResolver

initialization

   ExceptionHandlerExceptionResolver implements the interfaces of ApplicationContextAware and InitializingBean at the same time. In setApplicationContext(...), it only completes the injection function of applicationContext. The specific initialization process takes place in afterpropertieset().

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
		implements ApplicationContextAware, InitializingBean {
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBodyAdvice beans
		initExceptionHandlerAdviceCache();

		// Provide parameters for exception handling functions
		if (this.argumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}

		// Provides handling for the return value of the exception handling function
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

	private void initExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}

		// Get all beans annotated by @ ControllerAdvice
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException(...);
			}
			
			// Filter out all methods annotated by @ ExceptionHandler, and analyze the exceptions that can be handled by the method. Register with the exception that can be handled as key and the current method as value
			// In this way, when a global exception occurs, the corresponding processing method can be found according to the exception type
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
			if (resolver.hasExceptionMappings()) {
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
			}
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				this.responseBodyAdvice.add(adviceBean);
			}
		}
	}

	protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		return resolvers;
	}

	protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
		handlers.add(new ModelAndViewMethodReturnValueHandler());
		handlers.add(new ModelMethodProcessor());
		handlers.add(new ViewMethodReturnValueHandler());
		handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
		handlers.add(new ModelAttributeMethodProcessor(false));
		handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
		handlers.add(new ViewNameMethodReturnValueHandler());
		handlers.add(new MapMethodProcessor());
		if (getCustomReturnValueHandlers() != null) {
			handlers.addAll(getCustomReturnValueHandlers());
		}
		handlers.add(new ModelAttributeMethodProcessor(true));

		return handlers;
	}
}

public class ExceptionHandlerMethodResolver {
	public ExceptionHandlerMethodResolver(Class<?> handlerType) {
		// Filter out all methods annotated by @ ExceptionHandler
		for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
			// Register the exception types that the current method can handle
			for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
				addExceptionMapping(exceptionType, method);
			}
		}
	}

	private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
		List<Class<? extends Throwable>> result = new ArrayList<>();
		// Put the value of the ExceptionHandler annotation on the method (an array with the element type Throwable) into the result
		detectAnnotationExceptionMappings(method, result);
		if (result.isEmpty()) {
			// If the ExceptionHandler annotation does not specify which type of exception to handle, then the parameter will handle what type of exception it has
			for (Class<?> paramType : method.getParameterTypes()) {
				if (Throwable.class.isAssignableFrom(paramType)) {
					result.add((Class<? extends Throwable>) paramType);
				}
			}
		}
		
		// If neither the exception type to be handled is specified with the exception handler annotation nor the exception type is specified with formal parameters, the program will throw an exception if there is an error
		if (result.isEmpty()) {
			throw new IllegalStateException(...);
		}
		
		return result;
	}
}	
exception handling
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
		implements ApplicationContextAware, InitializingBean {

	public Method resolveMethod(Exception exception) {
		return resolveMethodByThrowable(exception);
	}

	public Method resolveMethodByThrowable(Throwable exception) {
		Method method = resolveMethodByExceptionType(exception.getClass());
		// If the current exception handling method cannot be found, find the handling method according to the exception causing the exception, and recurse in turn
		if (method == null) {
			Throwable cause = exception.getCause();
			if (cause != null) {
				method = resolveMethodByExceptionType(cause.getClass());
			}
		}
		return method;
	}

	public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
		// Cache is used here again
		Method method = this.exceptionLookupCache.get(exceptionType);
		if (method == null) {
			// Get the handler function corresponding to the exception
			method = getMappedMethod(exceptionType);
			this.exceptionLookupCache.put(exceptionType, method);
		}
		return method;
	}

	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
		// Filter out all key s that can handle the current exception (that is, the exceptions of the parent class of the current exception among the registered exceptions)
		List<Class<? extends Throwable>> matches = new ArrayList<>();
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
			if (mappedException.isAssignableFrom(exceptionType)) {
				matches.add(mappedException);
			}
		}
		
		// If you find a method that can handle the current exception, sort it first, and then take the first one (that is, the one with the highest priority)
		// According to the inheritance depth from the current exception to the comparative exception, the smaller the depth is, the higher it is. For example, A - > b - > C, it is obvious that the depth from B to A is smaller than that from C to A
		if (!matches.isEmpty()) {
			matches.sort(new ExceptionDepthComparator(exceptionType));
			return this.mappedMethods.get(matches.get(0));
		} else {
			return null;
		}
	}
}	

RequestToViewNameTranslator

  this component is used to obtain the default viewName from the request (if ModeAndView already has a view, this component will not work). DefaultRequestToViewNameTranslator is used by default. You can register a bean named "viewNameTranslator" with the container to override the default component.

  the implementation classes provided in spring MVC are:

  • DefaultRequestToViewNameTranslator
public interface RequestToViewNameTranslator {
	String getViewName(HttpServletRequest request) throws Exception;
}

public class DispatcherServlet extends FrameworkServlet {
	// Get the Bean named "viewNameTranslator" from the ApplicationContext as the RequestToViewNameTranslator of the Web application
	private void initRequestToViewNameTranslator(ApplicationContext context) {
		try {
			this.viewNameTranslator = context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
		} catch (NoSuchBeanDefinitionException ex) {
			this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
		}
	}
}

ViewResolver

  this component is used to convert viewName into View object. InternalResourceViewResolver is used by default. You can register one or more bean s of the ViewResolver implementation class with the container to override the default component.

  the implementation classes provided in spring MVC are:

  • InternalResourceViewResolver: mainly used to parse JSP files into views
  • BeanNameViewResolver
  • ContentNegotiatingViewResolver
  • FreeMarkerViewResolver
  • ResourceBundleViewResolver
  • UrlBasedViewResolver
  • XmlViewResolver
public interface ViewResolver {
	// Render of view (...) Method to render the response and return different types of views, and their rendering methods are also different
	View resolveViewName(String viewName, Locale locale) throws Exception;
}

public class DispatcherServlet extends FrameworkServlet {
	// Get all viewResolver implementation classes or beans named "viewResolver" from ApplicationContext as the list < viewResolver > of the Web application
	// Only org. By default springframework. web. servlet. view. InternalResourceViewResolver
	private void initViewResolvers(ApplicationContext context) {
		this.viewResolvers = null;

		// this.detectAllViewResolvers defaults to true
		if (this.detectAllViewResolvers) {
			// Obtain beans of all ViewResolver implementation classes from ApplicationContext and sort them as list < ViewResolver > of Web application
			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 {
				// Get the Bean named "viewResolver" from the ApplicationContext as the list < viewResolver > of the Web application
				ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
				this.viewResolvers = Collections.singletonList(vr);
			} catch (NoSuchBeanDefinitionException ex) {
				...
			}
		}

		if (this.viewResolvers == null) {
			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
		}
	}
}

FlashMapManager

  this component is used to solve the problem of parameter passing in the redirection process. The SessionFlashMapManager is used by default. You can register a bean named "flashMapManager" with the container to override the default component.

  the implementation classes provided in spring MVC are:

  • SessionFlashMapManager: take session as the carrier, store the parameters in the session before resetting, obtain the parameters from the session and delete the parameters from the session when the redirection request arrives.
public interface FlashMapManager {
	FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
	void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}

public class DispatcherServlet extends FrameworkServlet {
	// Get the name of "flashbean" from the application named "mapmanager" in the Web application
	private void initFlashMapManager(ApplicationContext context) {
		try {
			this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
		} catch (NoSuchBeanDefinitionException ex) {
			this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
		}
	}
}

Access request

DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet -> GenericServlet -> Servlet

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
	// When the request reaches the Servlet container, the Servlet container will find the matching Servlet that has been registered in the Servlet container according to the requested url and call its service(...) method
	// For spring MVC, all URLs are mapped to the DispatherServlet, and the service that DispatherServlet works (...) Implemented in the parent class FrameworkServlet
	// In fact, all requests call processRequest(...) directly Method (doOptions(...) (except)
	protected void service(HttpServletRequest request, HttpServletResponse response) {		
		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		// Because the PATCH method is not defined in the Servlet (PATCH is an extension of PUT, not the method specified in HTTP protocol), it cannot be handed over to HttpServlet for processing
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
			processRequest(request, response);
		} else {
			// Call the service of HttpServlet (...) Method, which belongs to the content of Java EE. In fact, it calls the corresponding doXxx(...) according to the request method Methods, such as doGet(...)
			// And doXxx(...) Methods are implemented in FrameworkServlet, and almost all of them call processRequest(...) directly Method (doOptions(...) (except)
			super.service(request, response);
		}
	}

	// Request processing
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response) {
		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;

		// LocaleContextHolder contains a ThreadLocal < LocaleContext > variable, which is used to save a LocaleContext for each thread
		// It is convenient to obtain LocaleContext through getLocaleContext() of the static method of LocaleContextHolder (LocaleContextHolder is almost a static method, so it can be called directly anywhere)
		// There is only one method of LocaleContext, getLocale(), through which you can get the Locale object
		// If the LocaleContext of the current thread is set before this code (such as in the Filter of Servlet Programming), the LocaleContext is invalid in the following code
		// Before creating and setting a new LocaleContext, the purpose of obtaining the old LocaleContext is to restore the following code
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();		
		// Upon request, by this Localeresolver to complete the creation of localcontext
		LocaleContext localeContext = buildLocaleContext(request);

		// The principle is similar to that of LocaleContextHolder, except that ServletRequestAttributes provides other functions
		// Through ServletRequestAttributes, you can obtain HttpServletRequest, HttpSession and attributes saved in corresponding objects
		// The scope of action of spring web request and session is based on this object
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
		
		// Set the newly created LocaleContext and ServletRequestAttributes to the current thread (the following code will set them back to the original value when the request processing is completed)		
		// If a new thread is created in the process of request processing (such as in business logic service), the Locale object cannot be obtained in the new thread through this method
		initContextHolders(request, localeContext, requestAttributes);

		// WebAsyncManager is similar to AsyncContext in Servlet Programming. It is used for asynchronous programming (similar to asynchronous programming introduced by Servlet3). It has nothing to do with @ Async annotation
		// WebAsyncUtils will directly create a new WebAsyncManager and cache it into the attribute of the current ServletRequest
		// In this way, within the same request scope, through webasyncutils getAsyncManager(...) Will get the same WebAsyncManager object
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		// The asynchronous thread executed through asyncManager will carry out additional callback processing before and after the execution of custom code. Here, the callback processing of RequestBindingInterceptor type is registered
		// RequestBindingInterceptor is used to execute initContextHolders(...) before and after executing custom code With resetContextHolders(...) method
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		try {
			// This method completes the most important logic and is defined in the subclass DispatherServlet
			doService(request, response);
		} catch (Throwable ex) {
			failureCause = ex;
			throw ex;
		} finally {
			// Restore the LocaleContext and RequestAttributes of the current thread, so that the original value will still be obtained in subsequent operations (such as Filter programmed by Servlet)
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				// Request to end additional processing, such as destroying the Bean of request scope, updating Session, etc
				requestAttributes.requestCompleted();
			}
			// Publish a ServletRequestHandledEvent event. Listeners that inherit applicationlistener < ServletRequestHandledEvent > can listen to this event
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

	// Create LocaleContext
	protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
		// this.localeResolver completed the injection during initialization
		LocaleResolver lr = this.localeResolver;
		if (lr instanceof LocaleContextResolver) {
			return ((LocaleContextResolver) lr).resolveLocaleContext(request);
		} else {
			// LocaleContext is a functional interface, so a Lambda expression is used here to represent an anonymous inner class
			return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
		}
	}

	// Handling cross domain requests
	protected void doOptions(HttpServletRequest request, HttpServletResponse response) {
		if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
			processRequest(request, response);
			if (response.containsHeader("Allow")) {
				return;
			}
		}
		
		super.doOptions(request, new HttpServletResponseWrapper(response) {
			@Override
			public void setHeader(String name, String value) {
				if ("Allow".equals(name)) {
					value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
				}
				super.setHeader(name, value);
			}
		});
	}
}

public class DispatcherServlet extends FrameworkServlet {
	protected void doService(HttpServletRequest request, HttpServletResponse response) {
		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));
				}
			}
		}

		// Publish some common objects to the request domain so that they can be easily obtained elsewhere
		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) {
			// Get the parameters redirected through FlashMapManager, save them in the request, and clear these parameters from FlashMapManager (because these parameters are used only once in redirection)
			FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
			if (inputFlashMap != null) {
				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
			}
			// Create an empty parameter holder when you want to redirect to store data later
			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
			// Publish the flashMapManager so that it can be easily obtained elsewhere
			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
		}

		try {
			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 {
				// If it is a file upload request, the request is encapsulated as MultipartHttpServletRequest and returned
				processedRequest = checkMultipart(request);
				// Is it a file upload request
				multipartRequestParsed = (processedRequest != request);

				// HandlerExecutionChain contains a handler and an array of handlerinterceptors
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					// When the processing method cannot be found, the 404 status is written directly to the response
					noHandlerFound(processedRequest, response);
					return;
				}

				// Gets the adapter that can handle the handler
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Before interceptor preprocessing, call the preHandle(...) of each element in the HandlerInterceptor array in sequence method
				// If a HandlerInterceptor's preHandle(...) If false is returned, no further calls will be made and false will be returned directly
				// If false is returned in the previous step, afterCompletion(...) of all handlerinterceptors will be called in reverse order method
				// If the call of interceptor chain finally returns false, the following controller and view processing will not be executed
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// The processing method is called through the adapter and the result is encapsulated as ModelAndView
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				// If asynchrony is enabled for the current thread, the view and post-processing will not be processed temporarily, and it will be returned directly to quickly release the thread
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
			
				// If there is no view in ModeAndView, obtain the default view from the request through viewNameTranslator
				applyDefaultViewName(processedRequest, mv);

				// The interceptor handler in each array is called in reverse order (...) method
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			} catch (Throwable ex) {
				dispatchException = ex;
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		} catch (Throwableex) {
			// Execute afterCompletion(...) of all interceptors method
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		} finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			} else {
				// If the file upload request is not asynchronous and has been processed, it needs to be cleared
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

	// If it is a file upload request, the request is encapsulated as MultipartHttpServletRequest and returned
	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
				if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
					logger.trace(...);
				}
			} else if (hasMultipartException(request)) {
				logger.debug(...);
			} else {
				return this.multipartResolver.resolveMultipart(request);
			}
		}
		
		return request;
	}

	// Traverse all registered HandlerMapping until HandlerExecutionChain is found
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

	// Traverse all registered handleradapters until the supports(...) of a HandlerAdapter Method returns true, indicating that the HandlerAdapter can handle the handler
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		
		throw new ServletException(...);
	}

	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, the previous operation should be performed first
		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			} else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				// Call the registered HandlerExceptionResolver successively until the return of a Resolver is not null. If NULL is finally returned, it means that all registered resolvers cannot handle the exception
				mv = processHandlerException(request, response, handler, exception);
				// If the ModelAndView returned in the previous step is null, it indicates that the exception has not been resolved, and ModelAndView is not the ModelAndView obtained by the exception
				errorView = (mv != null);
			}
		}

		if (mv != null && !mv.wasCleared()) {
			// Render the response through view
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			return;
		}

		if (mappedHandler != null) {
			// Execute afterCompletion(...) of all interceptors method
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) {
		Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		// Get view according to viewName
		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// Traverse all viewresolvers until the viewName can be converted to View
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException(...);
			}
		} else {
			view = mv.getView();
			if (view == null) {
				throw new ServletException(...);
			}
		}

		if (mv.getStatus() != null) {
			response.setStatus(mv.getStatus().value());
		}

		// Render the response through view
		view.render(mv.getModelInternal(), request, response);
	}
}

Posted by RobOgden on Fri, 20 May 2022 09:49:04 +0300