@[TOC] framework source code is a required course in our Coding promotion. SSM should be the most common framework for small partners. The SpringMVC initialization process is relatively simple, so today Songge will first analyze the SpringMVC initialization process with you.

Even if you haven’t seen the source code for SpringMVC, you’ve probably heard of it: DispatcherServlet is the brain of SpringMVC, which is responsible for the scheduling of the whole SpringMVC. It is the most core class in SpringMVC, and the whole top-level architecture design of SpringMVC is reflected here. So understand the DispatcherServlet source code, basically SpringMVC working principle is also clear.

However, the DispatcherServlet inherits from the FrameworkServlet, which in turn inherits from the HttpServletBean, as shown below:

So we start our analysis with HttpServletBeans.

1.HttpServletBean

The HttpServletBean inherits from the HttpServlet and is responsible for injecting init-param parameters into the properties of the current Servlet instance. It also provides the ability for subclasses to add requiredProperties. Note that HttpServletBeans do not depend on the Spring container.

The init method of an HttpServletBean is used to initialize an HttpServletBean.

@Override
public final void init(a) 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);
			}
			throwex; }}// Let subclasses do whatever initialization they like.
	initServletBean();
}
Copy the code

In this method, you first get all the configuration of the Servlet and convert it to PropertyValues, then modify the related properties of the target Servlet via BeanWrapper. BeanWrapper is a tool provided in Spring that allows you to modify the properties of an object like this:

public class Main {
    public static void main(String[] args) {
        User user = new User();
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(user);
        beanWrapper.setPropertyValue("username"."itboyhub");
        PropertyValue pv = new PropertyValue("address"."www.itboyhub.com");
        beanWrapper.setPropertyValue(pv);
        System.out.println("user = "+ user); }}Copy the code

Final output:

user = User{username='itboyhub', address='www.itboyhub.com'}
Copy the code

So the previous BW actually represents the current DispatcherServlet object.

One of the initBeanWrapper methods is empty when modifying the properties of the target Servlet using BeanWrapper, and developers can implement this method in subclasses and do some initialization if necessary.

After the properties are configured, the Servlet is eventually initialized by calling the initServletBean method, which is also an empty method and implemented in a subclass.

This is what HttpServletBean does, which is relatively simple: load servlet-related properties and set them to the current Servlet object, and then call initServletBean to continue initialization of the Servlet.

2.FrameworkServlet

FrameworkServlet#initServletBean () {FrameworkServlet#initServletBean ();

@Override
protected final void initServletBean(a) throws ServletException {
	/ / to omit...
	try {
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	}
	catch (ServletException | RuntimeException ex) {
		/ / to omit...}}Copy the code

This method is quite long, but when the log printing exception is thrown away, the core code that remains is actually two lines:

  1. Used to initialize WebApplicationContext initWebApplicationContext method.
  2. The initFrameworkServlet method is used to initialize the FrameworkServlet, but it is an empty method with no concrete implementation. Subclasses could have overridden this method to do some initialization, but they didn’t overwrite it, so we’ll ignore it for now.

So here is a matter of the most important initWebApplicationContext method, we look at:

protected WebApplicationContext initWebApplicationContext(a) {
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;
	if (this.webApplicationContext ! =null) {
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if(! cwac.isActive()) {if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); }}}if (wac == null) {
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		wac = createWebApplicationContext(rootContext);
	}
	if (!this.refreshEventReceived) {
		synchronized (this.onRefreshMonitor) { onRefresh(wac); }}if (this.publishContext) {
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}
	return wac;
}
Copy the code

The logic here is clear:

  1. First get the rootContext. By default, Spring sets the container to a property of the ServletContext with a key oforg.springframework.web.context.WebApplicationContext.ROOTSo using this key you can call ServletContext#getAttribute to get the rootContext.
  2. There are three possibilities for getting an instance of WebApplicationContext, that is, assigning a value to a WAC variable: 1. If the webApplicationContext is already assigned via the constructor, it is assigned directly to the WAC variable, and parent is set if necessary and refreshed if necessary. After this applies to Servlet3.0 environment, because from the beginning of the Servlet3.0, to support direct call ServletContext. Go to the registration Servlet addServlet method, When you manually register, you can use the WebApplicationContext prepared in advance. This is also described in my Spring Boot video. Interested partners can reply to VHR on the background of the official account to view the video details. 2. If the first failed to wac assignment, so try to call findWebApplicationContext method ServletContext lookup WebApplicationContext object, found the assignment to the wac; 3. If the second step fails to give wac assignment, then call createWebApplicationContext method create a WebApplicationContext object and assign a value to the wac, WebApplicationContext is typically created this way. After these three combinations, WAC must be worth it.
  3. When the ContextRefreshedEvent event is not triggered, Call onRefresh method to complete the container to refresh (due to the first and third WebApplicationContext way can eventually call configureAndRefreshWebApplicationContext method, and then publish event, The refreshEventReceived variable is marked as true, so it will actually refresh only if the WAC instance is retrieved in the second way, as you’ll see below).
  4. Finally, save the WAC into the ServletContext. The publishContext variable is saved or not based on the value of the publishContext variable, which can be configured using init-param when configuring the Servlet in web.xml for easy retrieval.

The these steps above, create WebApplicationContext object through createWebApplicationContext method needs and everyone in detail, as is generally WebApplicationContext created in this way. Let’s look at the relevant methods:

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);
	}
	configureAndRefreshWebApplicationContext(wac);
	return wac;
}
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());
	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();
}
Copy the code

There are two methods involved:

createWebApplicationContext

Get the create type, check the create type, call instantiateClass to instantiate the waC object, ConfigLocation is the SpringMVC configuration file path we configured in the web. XML file. The default file path is/web-INF /[servletName]-servlet.xml.

configureAndRefreshWebApplicationContext

ConfigureAndRefreshWebApplicationContext method mainly is the configuration & refresh WebApplicationContext, In this method, addApplicationListener is called to add a listener to the WAC. The listener listens for the ContextRefreshedEvent event. When the event is received, The onApplicationEvent method of the FrameworkServlet is called and the onRefresh method is called within that method to complete the refresh, after which the refreshEventReceived variable is marked as true.

public void onApplicationEvent(ContextRefreshedEvent event) {
	this.refreshEventReceived = true;
	synchronized (this.onRefreshMonitor) { onRefresh(event.getApplicationContext()); }}Copy the code

This is the general working logic of the FrameworkServlet#initServletBean method. The onRefresh method is involved here, but this is an empty method that is implemented in the DispatcherServlet subclass, so let’s look at DispatcherServlet.

3.DispatcherServlet

Without further ado, let’s go straight to the onRefresh method as follows:

@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}
Copy the code

InitStrategies are called in the onRefresh method for initialization. The content of initStrategies is simply the initialization of nine components. The nine initialization processes are similar. Here we use initViewResolvers as an example to look at the initialization process:

private void initViewResolvers(ApplicationContext context) {
	this.viewResolvers = null;
	if (this.detectAllViewResolvers) {
		// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
		Map<String, ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true.false);
		if(! matchingBeans.isEmpty()) {this.viewResolvers = new ArrayList<>(matchingBeans.values());
			// We keep ViewResolvers in sorted order.
			AnnotationAwareOrderComparator.sort(this.viewResolvers); }}else {
		try {
			ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
			this.viewResolvers = Collections.singletonList(vr);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default ViewResolver later.}}// Ensure we have at least one ViewResolver, by registering
	// a default ViewResolver if no other resolvers are found.
	if (this.viewResolvers == null) {
		this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties"); }}}Copy the code

The original viewResolvers variable is a collection into which the parsed viewResolvers objects will go.

First check whether the detectAllViewResolvers variable is true. If so, go directly to all viewResolvers in the Spring container, assign the results to viewResolvers, and sort. The detectAllViewResolvers variable is true by default and can be configured in web.xml if necessary, as follows:

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>
    <init-param>
        <param-name>detectAllViewResolvers</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
Copy the code

If detectAllViewResolvers is false, then the Spring container is then searched for a viewResolver named viewResolver, which is a separate viewResolver.

Generally speaking, you don’t need to configure the detectAllViewResolvers value in web.xml; you can load as many view parsers as you want.

As a simple example, we might configure the view parser in the SpringMVC configuration file like this:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
Copy the code

By default, the id of the bean is optional, and if it is, it can be whatever value it takes, because ultimately the view parser looks up the bean by type, not by ID. However, if you changed detectAllViewResolvers to false in web.xml, then the bean ID is important and must be viewResolver.

If the ViewResolver instance is not found in the Spring container either by type lookup or by ID lookup, the getDefaultStrategies method is called to get a default ViewResolver instance. The default instance can be obtained as follows:

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
	if (defaultStrategies == null) {
		try {
			// Load default strategy implementations from properties file.
			// This is currently strictly internal and not meant to be customized
			// by application developers.
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "'." + ex.getMessage());
		}
	}
	String key = strategyInterface.getName();
	String value = defaultStrategies.getProperty(key);
	if(value ! =null) {
		String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
		List<T> strategies = new ArrayList<>(classNames.length);
		for (String className : classNames) {
			try{ Class<? > clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); }catch (ClassNotFoundException ex) {
				throw new BeanInitializationException(
						"Could not find DispatcherServlet's default strategy class [" + className +
						"] for interface [" + key + "]", ex);
			}
			catch (LinkageError err) {
				throw new BeanInitializationException(
						"Unresolvable class definition for DispatcherServlet's default strategy class [" +
						className + "] for interface [" + key + "]", err); }}return strategies;
	}
	else {
		returnCollections.emptyList(); }}Copy the code

This code is actually quite simple, just reflection to get the default view parser.

First defaultStrategies assignment, defaultStrategies value is actually from the DispatcherServlet. The properties file is loaded into the, we’ll look at the content of the file:

As you can see, there are eight default key-value pairs defined, some with one value and some with multiple values. Not all projects have file uploads, and even if there are file uploads, Which specific MultipartResolver to use is also unclear, but it is up to the developer to decide.

DefaultStrategies actually loaded into the eight key/value pair is the view of the parser is corresponding to the org. Springframework. Web. Servlet. The InternalResourceViewResolver, An instance of this class is created by reflection, which is the default view parser when there are no view parsers in the Spring container.

This is the initViewResolvers workflow, and the other eight are similar, with the only difference being initMultipartResolver, as follows:

private void initMultipartResolver(ApplicationContext context) {
	try {
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.multipartResolver = null; }}Copy the code

As you can see, it just looks up the bean instance based on the bean name, not the default MultipartResolver.

With that said, Songo wants to tell you a little bit more about SpringMVC configuration,

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
</bean>
Copy the code

The id of the view resolver must be multipartResolver, and the id of the file upload resolver must be multipartResolver.

4. Summary

SpringMVC initialization process includes three instances of HttpServletBean, FrameworkServlet and DispatcherServlet. HttpServletBean basically loads the various properties of the Servlet configuration and sets them on the Servlet; The FrameworkServlet primarily initializes the WebApplicationContext; The DispatcherServlet primarily initializes its nine components.

This is just the initial flow, but what about the flow of the request when it arrives? This Songko next article to share with you ~ well, today’s first and small friends chat so much.