DispatcherServlet is the brain of SpringMVC, it is responsible for the whole SpringMVC dispatching work, it is the most core class in SpringMVC, the whole top-level architecture design of SpringMVC is reflected here. So if you understand the source code of DispatcherServlet, you will have a good idea of how SpringMVC works.

However, DispatcherServlet inherits from FrameworkServlet, FrameworkServlet inherits from HttpServletBean, as follows.

DispatcherServlet

So our analysis starts with the HttpServletBean.

1. HttpServletBean

HttpServletBean inherits from HttpServlet, it is responsible for injecting the parameters in init-param into the properties of the current Servlet instance, and also provides the ability to add requiredProperties to the subclasses, it should be noted that HttpServletBean does not depend on the Spring container.

As you know, HttpServlet initialization starts with the init method, so let’s start with the HttpServletBean’s init method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
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.
 initServletBean();
}

In this method, all the configuration of the Servlet is first obtained and converted to PropertyValues, and then the relevant properties of the target Servlet are modified through the BeanWrapper, a tool provided in Spring that can be used to modify the properties of an object, like the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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);

        // Output:
        // user = User{username='itboyhub', address='www.itboyhub.com'}
    }
}

So the bw in front actually represents the current DispatcherServlet object.

When modifying the properties of the target Servlet through BeanWrapper, there is an initBeanWrapper method that is empty. The developer can implement this method in a subclass if needed and perform some initialization operations.

After the properties are configured, the initServletBean method is finally called to initialize the Servlet. However, this method is also an empty method, which is implemented by the subclass.

This is what HttpServletBean does, it is relatively simple, load the Servlet related properties and set them to the current Servlet object, then call the initServletBean method to continue to complete the Servlet initialization operation.

2. FrameworkServlet

From the previous introduction, we know that the entry method of FrameworkServlet initialization is initServletBean, so let’s start from FrameworkServlet#initServletBean method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Override
protected final void initServletBean() throws ServletException {
 //...
 try {
  this.webApplicationContext = initWebApplicationContext();
  initFrameworkServlet();
 }
 catch (ServletException | RuntimeException ex) {
  //...
 }
}

This method is originally quite long, but leaving aside printing logs, exception handling, etc., the remaining core code is actually just two lines.

  1. initWebApplicationContext method is used to initialize the WebApplicationContext.
  2. initFrameworkServlet method is used to initialize the FrameworkServlet, but this method is an empty method, no specific implementation. Originally subclasses can override the method to do some initialization operations, but in fact, subclasses do not override the method, so we will ignore this method for the time being, not to analyze.

So the most important thing here is actually initWebApplicationContext method, let’s look at it together.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protected WebApplicationContext initWebApplicationContext() {
 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;
}

The logic here is also relatively clear.

  1. By default, Spring sets the container as an attribute of the ServletContext with the key org.springframework.web.context.WebApplicationContext.ROOT, so based on this key, you can call ServletContext#getAttribute method to get the rootContext.

  2. Get the WebApplicationContext instance, which is the process of assigning a value to the wac variable, there are three possibilities here.

    1. If you have already assigned a value to the webApplicationContext through the constructor method, you will directly assign it to the wac variable, and at the same time, if you need to set the parent on the set, need to refresh on the refresh. This approach is applicable to the environment after Servlet3.0, because from Servlet3.0 onwards, only to support direct calls to ServletContext.addServlet method to register Servlet, manual registration can use their own WebApplicationContext prepared in advance.
    2. If the first step does not succeed in assigning a value to wac, then call the findWebApplicationContext method to try to find the WebApplicationContext object in the ServletContext, and assign a value to wac when you find it.
    3. If the second step does not succeed in assigning a value to wac, then call the createWebApplicationContext method to create a WebApplicationContext object and assign it to wac. generally speaking, the WebApplicationContext is created in this way.

    After these three steps, wac must have a value.

  3. When the ContextRefreshedEvent event is not triggered, call the onRefresh method to finish refreshing the container.

    Since the first and third ways of getting WebApplicationContext will eventually call configureAndRefreshWebApplicationContext method, then publish the event, and then mark the refreshEventReceived variable as true, so actually only the When the second way to get the wac instance, it will be refreshed here, see the analysis below.

  4. Finally, wac will be saved to the ServletContext. Save will be based on the value of the publishContext variable to decide whether to save, publishContext can be configured in the web.xml when configuring the Servlet through init-param, save the purpose is to facilitate access.

Among the above steps, the createWebApplicationContext method that creates the WebApplicationContext object needs to be explained in detail, because this is how the WebApplicationContext is usually created.

Let’s take a look at the related methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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();
}

There are two methods involved here.

createWebApplicationContext

First get the creation type, and check the creation type, if there is no problem call instantiateClass method to complete the creation work. Then configure various properties for the created wac object. The configLocation is the path to the SpringMVC configuration file we configured in the web.xml file, the default file path is /WEB-INF/[servletName]-servlet.xml.

configureAndRefreshWebApplicationContext

configureAndRefreshWebApplicationContext method is also mainly to configure & refresh the WebApplicationContext, in this method will call addApplicationListener to add a listener for wac, listening to the ContextRefreshedEvent event, when the event is received, the onApplicationEvent method of the FrameworkServlet will be called, and the onRefresh method will be called in this method to finish refreshing, after refreshing, the refreshEventReceived variable will be marked as After the refresh, the refreshEventReceived variable will be marked as true.

1
2
3
4
5
6
public void onApplicationEvent(ContextRefreshedEvent event) {
 this.refreshEventReceived = true;
 synchronized (this.onRefreshMonitor) {
  onRefresh(event.getApplicationContext());
 }
}

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

3. DispatcherServlet

Let’s cut the crap here and go straight to the onRefresh method, as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@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);
}

initStrategies is called in the onRefresh method for initialization. initStrategies is actually very simple, it is the initialization of nine components. The initialization process of the nine is relatively similar, so let’s take the common view resolver initialization method initViewResolvers as an example and look at the initialization process together.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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");
  }
 }
}

The viewResolvers variable at the beginning is a collection into which the parsed view resolver objects will be placed.

The first step is to determine if the detectAllViewResolvers variable is true, and if it is, then it will look up all the view resolvers in the Spring container, assign the result to the viewResolvers, and then sort them. By default the value of the detectAllViewResolvers variable is true, but if needed, it can be configured in web.xml, like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<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>

If the value of detectAllViewResolvers is false, then the Spring container will look for a view resolver named viewResolver, which is a separate view resolver.

In general, we don’t need to configure the value of detectAllViewResolvers in web.xml, we load as many view resolvers as there are.

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

1
2
3
4
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

By default, the bean’s id is optional. If it does, it can take any value, because ultimately the view resolver will look for it by type, not by id. But if you change detectAllViewResolvers to false in web.xml, then the value of the bean’s id is more important and must be viewResolver.

If no ViewResolver instance is found in the Spring container in either of these two ways (by type or by id), then the getDefaultStrategies method will be called to get a default ViewResolver instance. The default instance is obtained in the following way.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 {
  return Collections.emptyList();
 }
}

This code is actually relatively simple, it is to get the default view parser through reflection.

First assign a value to defaultStrategies, the value of defaultStrategies is actually loaded from the DispatcherServlet.properties file, let’s look at the contents of this file.

DispatcherServlet.properties

As you can see, there are 8 default key-value pairs defined here, some with one value and some with multiple values. The initStrategies method in the previous section initializes nine components, but only eight are defined here by default, one MultipartResolver is missing.

This is understandable, as not all projects have file uploads. And even if there are file uploads, it is not easy to determine which specific MultipartResolver to use, so it is up to the developer to decide.

defaultStrategies actually loads these 8 key-value pairs, where the view resolver corresponds to org.springframework.web.servlet.view.InternalResourceViewResolver. When no view resolver exists in the Spring container, the default view resolver is this.

This is the workflow of initViewResolvers, the other 8 are similar to it, the only difference is initMultipartResolver, as follows.

1
2
3
4
5
6
7
8
private void initMultipartResolver(ApplicationContext context) {
 try {
  this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
 }
 catch (NoSuchBeanDefinitionException ex) {
  this.multipartResolver = null;
 }
}

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

Speaking of which, let’s talk about a small detail in the SpringMVC configuration.

1
2
3
4
5
6
<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>

The above configuration of the view parser and file upload parser, I wonder if you have noticed that the id of the view parser can be optional, while the id of the file upload parser must be multipartResolver, review our source code analysis above, you will know why!

4. Summary

Well, this is the initialization process of SpringMVC, which mainly involves HttpServletBean, FrameworkServlet and DispatcherServlet instances. HttpServletBean is mainly to load the various properties of the Servlet configuration and set to the Servlet. FrameworkServlet is mainly to initialize the WebApplicationContext. DispatcherServlet initializes nine of its own components.

Reference: https://mp.weixin.qq.com/s/JImMPTGzHjGPRnRJlQlDhg