Spring MVC 5 – Spring MVC configuration and DispatcherServlet initialization process

Today’s content is the initialization process of SpringMVC, which is actually the initialization process of DispatcherServilet.

Special Bean Types

DispatcherServlet delegates the following special beans to handle requests and render correct returns. These special beans are beans managed by the Spring MVC framework and handle related requests according to the conventions of the Spring framework. They are generally built-in within the framework. Of course, we can also customize or extend their functions.

These special beans include:

  1. HandlerMapping: Map the request to the corresponding HandlerMapping according to certain rules for processing. HandlerMapping can contain a series of interceptors for pre- or post-processing. The framework provides two HandlerMappings by default, RequestMappingHandlerMapping (which handles @RequestMapping annotation methods) and SimpleUrlHandlerMapping.
  2. HandlerAdapter: After HandlerMapping matches the request, HandlerAdapter is called to specifically process the request.
  3. HandlerExceptionResolver: Exception handler after an exception occurs.
  4. ViewResolver: processing returns
  5. LocaleResolver, LocaleContextResolver: localization processor
  6. ThemeResolver: Theme rendering processor
  7. MultipartResolver: Multipart processor, processing of file upload and download.
  8. FlashMapManager: A handler that stores and gets “input” and “output” across requests

Web MVC Config

During the initialization process of DispatcherServlet, the initialization of the above special beans will be completed according to the configuration of WebApplicationContext (xml or annotation method, analyzed in the previous two articles). If DispatcherServlet does not find the corresponding configuration in WebApplicationContext, the default configuration in the DispatcherServlet.properties file will be used. Complete initialization.

The DispatcherServlet.properties file is under the Spring web mvc package:

We guess that the Spring MVC framework completes the initialization of the above special beans through the init method of DispatcherServlet. Next, we will analyze the specific initialization process in detail.

Servlet Config

The specific method of initializing DispatcherServlet through annotation or xml has been analyzed in the previous two articles and will not be repeated here.

Initialization of DispatcherServlet

As we all know, a Servlet container (such as Tomcat) will complete the initialization of the Servlet by calling the Servlet’s init method.

Let’s take a look at the initialization process of DispatcherServlet, which is the init method of DispatcherServlet.

Let’s first take a look at the class structure of DispatcherServlet:

The init method is in his parent class HttpServletBean:

 @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();
}

The above code is the processing of the current Servlet attributes and has nothing to do with our goal. The initialization logic is in the bottom method initServletBean, in its subclass (also the direct parent class of DispatcherServlet) FrameworkServlet:

 protected final void initServletBean() throws ServletException {
        ...omit part of the code
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}

There are a lot of codes for printing logs in this method. Ignore them. The rest are the calls of two methods: one is to create webApplicationContext, and the other is initFrameworkServlet. This initFrameworkServlet is an empty method, so the key to the initialization logic of DispatcherServlet is this. initWebApplicationContext() method.

The initWebApplicationContext method is very long, let’s analyze it in sections.

 protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
        ...

First get the RootContext of the current ServletContext. For RootContext, see the previous article Spring MVC 4: Context hierarchy.

Then:

 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
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}

Determine if the WebApplicationContext has been initialized in the construction method when the DispatcherServlet object is created, then use the WebApplicationContext and set the RootContext obtained above as the parent container of the current WebApplicationContext. And determine whether the Context has been refreshed. If not, call the configureAndRefreshWebApplicationContext method to configure and refresh the Context.

In the previous article Spring MVC 3: Based on annotation configuration, we analyzed the creation process of DispatcherServlet. It is true that the created ServletContext is passed through the parameters of the constructor during creation:

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

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

        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

   ...code omitted

So if it is configured through annotations, the ServletContext will be created through the createServletApplicationContext() method:

 @Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}

The final ServletContext created is AnnotationConfigWebApplicationContext.

So if you configure it through annotations, you have to follow the logic above.

Otherwise, if it is configured not through annotations but through xml, that is to say, there is no ServletContext when the DispactherServlet is created, it will go to the following logic:

 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
wac = createWebApplicationContext(rootContext);
}

If wac is empty (it was not set when DispatcherServlet was created), then it will be judged whether the container has been registered. If it has been registered, Spring framework will think that its parent container has been set, and has also been initialized and refreshed. Yes, just take it and use it. (If our application does not actively register, there will be no registered Context, so this code will not run).

Then look at the code below. If not found, call createWebApplicationContext to create it. After creating the WebApplicationContext, the createWebApplicationContext method will also set its parent container to RootContext. Then it will also call configureAndRefreshWebApplicationContext to configure and refresh the container. Go to the first step above (through Annotation configuration, DispatcherServlet has already set a Context through the constructor when it is created) and has consistent logic.

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);
}
configureAndRefreshWebApplicationContext(wac);

return wac;
}

First call the getContextClass() method to get the contextClass:

 public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

public Class<?> getContextClass() {
return this.contextClass;
}

It can be seen that if it is started not through annotations but through xml configuration, the created ServletContext should be this XmlWebApplicationContext.

After creating the ServletContext, configure it in the same way as xml: set the parent container, and then call the configureAndRefreshWebApplicationContext method to configure and refresh the container.

Next we look at the configureAndRefreshWebApplicationContext method.

configureAndRefreshWebApplicationContext

So far, our previous guess: initialize each special bean through the init method of DispatcherServlet. Not confirmed yet – in the init method of DispatcherServlet, we have not seen the relevant initialization code.

However, the code analysis has not been completed yet. There is also configureAndRefreshWebApplicationContext. Let’s continue the analysis.

The code is relatively long, so let’s analyze it in sections:

 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());
}
}

Set the Id for the WebApplicationContext, it doesn’t matter, continue to look at the following code:

 wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

Set the ServletContext, ServletConfig, and namespace, and then add a new listener: ContextRefreshListener().

Then:

 // 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();
}

Set environment variables, obtain initialization parameters, and finally call the refresh method of WebApplicationContext.

Still not see the initialization of special beans by DispatcherServlet! Moreover, the current code logic is transferred to the ApplicationContext, which is the content of Spring Framework, not Spring MVC.

Don’t worry, you’re about to touch the switch!

The current code is indeed transferred to the Spring Framework. Therefore, the Spring family bucket, whether it is Spring MVC, Spring Boot, or Spring Security, is all based on the Spring Framework. Mastering the Spring Framework is the basis for mastering the Spring family bucket.

We are very familiar with the refresh method of ApplicationContext. It is a key method of Spring Framework and is implemented in the AbstractApplicationContext class. This method will finally call the finishRefresh() method:

The finishRefresh() method will finally publish the ContextRefreshedEvent event.

Yes, during the previous code analysis process, we did register a listener for this event, ContextRefreshListener, in the WebApplicationContext container:

 private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}

The listener is an internal class defined in FrameworkServlet. Its onApplicationEvent method will call the onApplicationEvent method of FrameworkServlet. In this way, through the listening mechanism, the code logic is transferred back to DispatcherServlet (to be precise, its parent class FrameworkServlet). Got:

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

Eventually it will be called to DispatcherServlet:

 @Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}

Looking at the DispatcherServlet code, we will find that this initStrategies is the method we are looking for. The method parameter Context is passed through the event. Therefore, the DispatcherSerlet can hold the ApplicationContext object when initializing, and then do whatever you want. Complete the initialization of Spring MVC special beans.

For space reasons, we will analyze the specific initialization process of DispatcherServlet later.

Previous article Spring MVC 4: Context level