Probably the most voluminous spring source code series: Spring MVC

This paper configures spring MVC based on full annotation, so the source code analysis is annotation, but the implementation of xml is also similar. As long as you understand one way, it's not a problem to understand the other.
First, briefly recall the steps of using spring MVC for xml:
1,web. The DispatcherServlet is configured on the XML, and the configuration file paths of rootContext and mvcContext are specified
2. Write configuration files for rootContext and mvcContext
Similarly, the annotation method is also these steps
1. Create a WebConfiguration that inherits AbstractAnnotationConfigDispatcherServletInitializer (specified by DispatcherServlet in this class), and specify the configuration classes of rootContext and mvcContext
2. Write the configuration of rootContext and mvcContext
It can be seen that the main steps are the same, except that the configuration class is changed from the xml file. The configuration class in step 2 can be understood that spring supports the use of configuration classes to configure containers, but how does tomcat change from xml to configuration classes in the first step? In fact, tomcat uses the spi mechanism, which can be understood as defining an interface for tomcat to allow users to provide implementation classes. tomcat can load the implementation classes and read the configuration. Here, spring provides us with implementation classes, which will be discussed later. Now let's create a fully annotated spring MVC environment.
The project structure is as follows

WebConfiguration

public class WebConfiguration extends AbstractAnnotationConfigDispatcherServletInitializer {

	/**
	 * The class returned with @ Configuration annotation will be used to define ContextLoaderListener
	 * Create beans in ApplicationContext (non web components)
	 */
	@Override
	protected Class<?>[] getRootConfigClasses() {

		return new Class<?>[] {ContextConfig.class};
	}

	/**
	 * Return the class with @ Configuration annotation to define the bean(web component) in the context of DispatcherServlet
	 */
	@Override
	protected Class<?>[] getServletConfigClasses() {

		return new Class<?>[] {MvcContextConfig.class};
	}

	/**
	 * Request path configuration. If it is configured as "/", it will process all requests
	 */
	@Override
	protected String[] getServletMappings() {

		return new String[] {"/"};
	}
	@Override
	protected Filter[] getServletFilters() {

		return new Filter[] {
				new CharacterEncodingFilter("utf-8",true)};
	}
}

ContextConfig

@ComponentScan(value="wang.haoxu",
		excludeFilters = { //Scan excluded content
				@ComponentScan.Filter(type= FilterType.ANNOTATION,
						classes={Controller.class})
		})
public class ContextConfig {

}

MvcContextConfig

@ComponentScan(value="wang.haoxu",
		includeFilters = { //Represents the content of the word scan
				@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class})
		},
		useDefaultFilters = false)
@EnableWebMvc
@PropertySource("classpath:application.properties")
public class MvcContextConfig implements WebMvcConfigurer {

	@Value("${web.view.prefix}")
	private String WebViewPrefix;

	@Value("${web.view.suffix}")
	private String WebViewSuffix;

	//Configure view parser
	@Bean
	public ViewResolver viewResolver() {
		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		resolver.setPrefix(WebViewPrefix);
		resolver.setSuffix(WebViewSuffix);
		return resolver;
	}

	/**
	 * Without this configuration, the dispatcher servlet will be mapped to the default servlet of the application,
	 * So he handles all requests, including static resources, such as style sheets for images
	 * This is not what we want
	 */
	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		configurer.enable();
	}
}

The annotation configuration of spring MVC is completed in three steps

Source code analysis

Let's first look at where spring and tomcat interact. Just now, tomcat uses the spi mechanism to implement the configuration class. Where is spring implemented?


Let's first look at its parent class, which is provided by the servlet package


This implementation class is defined in the spring web module and will be called by tomcat

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	/**
	 * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
	 * implementations present on the application classpath.
	 * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
	 * Servlet 3.0+ containers will automatically scan the classpath for implementations
	 * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
	 * such types to the {@code webAppInitializerClasses} parameter of this method.
	 * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
	 * this method is effectively a no-op. An INFO-level log message will be issued notifying
	 * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
	 * no {@code WebApplicationInitializer} implementations were found.
	 * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
	 * they will be instantiated (and <em>sorted</em> if the @{@link
	 * org.springframework.core.annotation.Order @Order} annotation is present or
	 * the {@link org.springframework.core.Ordered Ordered} interface has been
	 * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
	 * method will be invoked on each instance, delegating the {@code ServletContext} such
	 * that each instance may register and configure servlets such as Spring's
	 * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
	 * or any other Servlet API componentry such as filters.
	 * @param webAppInitializerClasses all implementations of
	 * {@link WebApplicationInitializer} found on the application classpath
	 * @param servletContext the servlet context to be initialized
	 * @see WebApplicationInitializer#onStartup(ServletContext)
	 * @see AnnotationAwareOrderComparator
	 */
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

This class is provided by spring. Finally, tomcat will call the onStartup method of this class
@ HandlesTypes(WebApplicationInitializer.class) is declared on this class. tomcat will scan out all the implementation classes of WebApplicationInitializer class, and then pass the parameters to onStartup method. We can see that it will eventually call onStartup method of WebApplicationInitializer
The implementation class here has only one WebConfiguration. Let's take a look at its inheritance relationship first

His onStartup method is implemented by the AbstractDispatcherServletInitializer class. Let's track it

super.onStartup(servletContext);


Finally realized here

protected void registerContextLoaderListener(ServletContext servletContext) {
		// Create root container
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			// spring implements the listener in sevlet s
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}

This completes one thing, creating a root container and passing the container into a listener

registerDispatcherServlet(servletContext);

protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");

		// Create webContext
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

		// webContext passes in dispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
		// tomcat adds a servlet and a dispatcher servlet implemented by spring
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}

		registration.setLoadOnStartup(1);
		// mapping of dispatcherServlet
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());

		// Add filter
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}

Completed the registration of dispatcherServlet and the creation of mvcContext
After performing the above steps, a Servlet in tomcat is dispatcher Servlet, and two containers are created. rootContext is referenced by listener and mvctContext is referenced by dispatcher Servlet
Inheritance relationship of dispatcherServlet:

After the Servlet is created, the init() method will be executed. The init method of dispatcherServlet is implemented in HttpServletBean

public final void init() throws ServletException {

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		// Initializing containers and servlets
		initServletBean();
	}

The main process is completed in initServletBean, which is implemented in FrameworkServlet

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

		try {
			// Initial spring container
			this.webApplicationContext = initWebApplicationContext();
			// Initial Servlet null method
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

Initialize container
```java
protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

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

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

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

		return wac;
	}

createWebApplicationContext

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

		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		// Refresh the container and add a listener to the container
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		// Added a listener for spring startup
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		wac.refresh();
	}

When this method is executed, the event listener is executed
applyInitializers
refresh container

Listener execution


When receiving the request, it will execute the service method, which is implemented by the FrameworkServlet. The service will call doservice. The doservice is implemented by the dispatcher servlet, and the doservice calls the doDispatch method

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// What you get is the method in the controller
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// Remove the adapter according to the controller
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				// Execute preHandle
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// Call the method in the controller
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

				applyDefaultViewName(processedRequest, mv);
				// Execute postHandle
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

Keywords: Java Spring Tomcat

Added by Gladiator2043 on Fri, 21 Jan 2022 03:50:06 +0200