Working principle analysis of high performance service middleware Tomcat

Posted by vestax1984 on Mon, 03 Jan 2022 00:44:50 +0100

IX. spring integrates tomcat core

9.1 core ideas

We may have questions. Both the spring MVC framework and the spring boot framework need to embed a Tomcat service middleware. Of course, it may also be Jetty. Since this article mainly talks about tomcat, we should ask what Tomcat did when it was started?

<web-app>
	<listener>
   	 	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
             </listener>
	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>/WEB-INF/root-context.xml</param-value>
	</context-param>
	<servlet>
    		<servlet-name>app1</servlet-name>
   	 	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    	<init-param>
      	 	 <param-name>contextConfigLocation</param-name>
       	 	<param-value>/WEB-INF/app1-context.xml</param-value>
   	 </init-param>
    		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
   		 <servlet-name>app1</servlet-name>
    		<url-pattern>/app1/*</url-pattern>
	</servlet-mapping>
	</web-app>

No matter how Spring MVC integrates Tomcat or Spring boot integrates tomcat, there is a basic core idea. Then we can see how these middleware are integrated with the Spring family; How Tomcat completes the Spring life cycle process at startup, loads all bean s and exposes ports to the public; How can the request be distributed to our background business code according to the path? So let's say what the Tomcat startup mainly does for Spring, which is the core So what did we do? We can according to the web XML configuration to guess

  1. When tomcat is started, ContextLoaderListener calls the refresh() method of {AbstractApplicationContext class to complete the loading of Spring life cycle
  2. When DispatcherServlet tomcat is started, subclasses will be defined for the HttpServlet of tomcat to accept and distribute client requests to the server

Therefore, based on the above analysis, we can conclude that when tomcat is started, it does nothing more than two things for spring. One is to complete the spring life cycle task, and the other is to accompany a dispatcher servlet to the ServletContext context as the smallest receiving unit for request reception

9.2 spi mechanism

When talking about spring MVC integrating tomcat, we need to talk about the spi mechanism first, because Tomcat uses a spi like way to load classes during integration

9.2.1 SPI action

Think about a scenario. We encapsulate a set of services. Others can use these interface APIs to complete the corresponding operations by introducing the packages we have written. There is no problem, but the entities using the service have different business requirements and need to be further expanded. However, since the API is well written, it is not so simple to expand, If there is such a scene, what should we do? java has provided its own spi mechanism

The full name of SPI is Service Provider Interface, which is a mechanism provided by Java for third-party implementation and extension. Through this mechanism, we can realize decoupling. SPI interface party is responsible for defining and providing default implementation, and SPI caller can extend as needed

 

Java SPI is actually a dynamic loading mechanism realized by the combination of "interface based programming + policy mode + configuration file".

There are often many different implementation schemes for each abstraction of system design. In object-oriented design, it is generally recommended to program based on the interface between modules, and the implementation classes are not hard coded between modules. Once a specific implementation class is involved in the code, it violates the principle of pluggability. If you need to replace an implementation, you need to modify the code. In order to realize the module assembly without dynamic indication in the program, a service discovery mechanism is needed.
Java SPI provides such a mechanism: a mechanism to find a service implementation for an interface. Similar to the idea of IOC, it is to move the control of assembly outside the program. This mechanism is particularly important in modular design. So the core idea of SPI is decoupling.

9.2.2 usage scenarios

Generally speaking, it is applicable to the implementation strategy that the caller enables, extends, or replaces the framework according to the actual use needs

Common examples:

  1. The database driver loading interface implements the addition of classes, and JDBC loads the drivers of different types of databases
  2. The log facade interface implements class loading, and SLF4J loads log implementation classes from different providers
  3. Spring makes a lot of use of SPI, such as servlet3 0 specification's implementation of ServletContainerInitializer, automatic Type Conversion SPI(Converter SPI, Formatter SPI), etc
  4. Dubbo also makes extensive use of SPI to extend the framework, but it encapsulates the native SPI provided by Java, allowing users to extend and implement the Filter interface

9.2.3 introduction to use

To use Java SPI, you need to follow the following conventions:

1. After the service provider provides a specific implementation of the interface, create a file named "fully qualified name of the interface" in the META-INF/services directory of the jar package, and the content is the fully qualified name of the implementation class;

2. The jar package where the interface implementation class is located is placed in the classpath of the main program;

3. The main program passes Java util. Serviceloder dynamically loads the implementation module, which finds the fully qualified name of the implementation class by scanning the configuration file in the META-INF/services directory, and loads the class into the JVM;

4. The implementation class of SPI must carry a construction method without parameters;

9.2.4 example code

 

As shown in the above project, we can see that I have defined two implementations of the interface in separate projects

Spi-api

public interface HelloService {
      public void sayHello();
    }

Spi-provider

 

public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("HelloServiceImpl.......");
    }
}

As shown above, under META-INF, the services file is introduced to specify the implementation class of the interface, and then the interface is xinah in the class

Spi-provider-other

public class HelloServiceImplOther implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("HelloServiceImplOther.......");
    }
}

The structure and implementation of the other project are the same as the previous project

public static void main(String[] args) {
    ServiceLoader<HelloService> load = ServiceLoader.load(HelloService.class);
    for (HelloService helloService:load) {
        helloService.sayHello();
    }
}

As you can see, this is the result of spi execution

X. spring MVC integrates tomcat

So why do we introduce spi? In fact, Spring MVC uses the integration mechanism of spi when integrating tomcat Now let's see how to deal with it Of course, spi is just a design idea. java has the spi implementation mechanism of java, so Spring will also have the Spring implementation mechanism So how does tomcat pull up Spring at startup and complete the whole life cycle of Spring Here we will review the startup process of tomcat There is such a line of code in the tomcat startup process

if (engine != null) {
    synchronized (engine) {
        engine.start();
    }
}

You can see that when tomcat starts, it will start. The engin e container of tomcat and the start() method will start the thread to start all containers So let's imagine when we start an application for tomcat That must be when you start the context container, which means you start the application At this time, when we start the context container, if we don't want to load the servlet configured by xml Then we can only add a servlet to the context container to accept the request distribution from the client OK, let's see what the context container does The code when the context starts The following code is the core code when tomcat starts

protected synchronized void startInternal() throws LifecycleException {
    	/**Omit some codes*/
        fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

            /**Omit some codes*/
        for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
            initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(),getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }
/**Omit some codes*/
if (ok) {
         if (!listenerStart()) {
           log.error(sm.getString("standardContext.listenerFail"));
           ok = false;
      }
}

The above code indicates that Servlet and ContextLoaderListener are added to the ServletContext to start the Spring container Through this launcher, you can complete the Spring life cycle

10.1 embedded Servlet

public static final String CONFIGURE_START_EVENT = "configure_start";

protected void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    for (LifecycleListener listener : lifecycleListeners) {
        listener.lifecycleEvent(event);
    }
}

We can see that the contextconfig class implements the LifecycleListener interface, so all classes that implement this interface will call the lifecycleEvent method. Let's see what the contextconfig class does

public void lifecycleEvent(LifecycleEvent event) {
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) { }
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        if (originalDocBase != null) {  context.setDocBase(originalDocBase);}
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }
}

f (event.getType(). Equals (lifecycle. Configuration_start_event)) {configurestart(); grabs the core code. OK, let's see what the observer does after the context is started in the observer

protected synchronized void configureStart() {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.start"));
    }
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.xmlSettings",
                context.getName(), Boolean.valueOf(context.getXmlValidation()),
                Boolean.valueOf(context.getXmlNamespaceAware())));
    }
    webConfig();
    if (!context.getIgnoreAnnotations()) {
        applicationAnnotationsConfig();
    }
    if (ok) {
        validateSecurityRoles();
    }
    if (ok) {
        authenticatorConfig();
    }
    if (log.isDebugEnabled()) {
        log.debug("Pipeline Configuration:");
        Pipeline pipeline = context.getPipeline();
        Valve valves[] = null;
        if (pipeline != null) { valves = pipeline.getValves();}
        if (valves != null) {
            for (Valve valve : valves) {
                log.debug("  " + valve.getClass().getName());
            }
        }
    }
    if (ok) {
        context.setConfigured(true);
    } else {
        log.error(sm.getString("contextConfig.unavailable"));
        context.setConfigured(false);
    }
}

The core code of webConfig() is in this method, so what is done in this method? We can follow in and have a look

protected void webConfig() {
    	/**Omit some codes*/  
if (ok) {
           processServletContainerInitializers();
    }
}

As shown above is our core code Let's see what the processServletContainerInitializers() method does?

protected void processServletContainerInitializers() {
    List<ServletContainerInitializer> detectedScis;
    try {
        WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
        detectedScis = loader.load(ServletContainerInitializer.class);
    } catch (IOException e) {
        log.error(sm.getString("contextConfig.servletContainerInitializerFail",context.getName()),e);
        ok = false;
        return;
    }
    for (ServletContainerInitializer sci : detectedScis) {
        initializerClassMap.put(sci, new HashSet<Class<?>>());
        HandlesTypes ht;
        try {
            ht = sci.getClass().getAnnotation(HandlesTypes.class);
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.info(sm.getString("contextConfig.sci.debug",sci.getClass().getName()),e);
            } else {
                log.info(sm.getString("contextConfig.sci.info",sci.getClass().getName()));
            }
            continue;
        }
        if (ht == null) {continue;}
        Class<?>[] types = ht.value();
        if (types == null) {continue;}
        for (Class<?> type : types) {
            if (type.isAnnotation()) {
                handlesTypesAnnotations = true;
            } else {
                handlesTypesNonAnnotations = true;
            }
            Set<ServletContainerInitializer> scis = typeInitializerMap.get(type);
            if (scis == null) {
                scis = new HashSet<>();
                typeInitializerMap.put(type, scis);
            }
            scis.add(sci);
        }
    }
}

detectedScis = loader.load(ServletContainerInitializer.class); The core code loads all the servletcontainerinitializer implementation classes under {META-INF/services / in the context directory See, here is a spi mechanism similar to java, but tomcat has implemented it by itself Use the WebappServiceLoader class to scan all the interface files under the META-INF/services path under the whole context path, then find the implementation class of the interface, obtain the annotation @ HandlesTypes, and then make a mapping between the annotated value and the map of the implementation class Well, when the mapping is made, it will be executed naturally

contextConfig class

if (ok) {
    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) {
        if (entry.getValue().isEmpty()) {
            context.addServletContainerInitializer(entry.getKey(), null);
        } else {
            context.addServletContainerInitializer( entry.getKey(), entry.getValue());
        }
    }
}

As shown in the core code above, add the mapping class to the private map < servletcontainerinitializer, set < class <? > > initializers =new LinkedHashMap<>(); In, when the context is started, this is the whole configuration_ Start event handling procedure So the whole?? After construction, we call the startup process of Standcontext

  /**Omit some codes*/
        for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
            initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(),getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }

As shown in the above code, the ServletContainerInitializer calls the onStartup() method of all subclasses of this class The onStartup() method of this class is cleverly used in the startup of spring MVC Therefore, as long as we use the SPI mechanism under spring MVC, we can drive spring to start Well, we see that tomcat starts all of the tomcat related containers by calling the interface ServletContainerInitializer. Here we can think about it. If we want to introduce spring MVC, when tomcat starts, we should actually inject the configured servlet mapping into the ServletContext In this way, the corresponding servlet class can be found when the request is executed Well, let's take a look here. Spring MVC cleverly uses our spi mechanism

As shown in the figure above, do you feel deja vu The interface of ServletContainerInitializer in tomcat defines an implementation class in spring web OK, so when tomcat starts, it will also call the onStartup() method of the SpringServletContainerInitializer class Next, let's look at what the source code of this class does?  

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if(webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();
            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if(!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)waiClass.newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }
        if(initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();
            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }
        }
    }
}

As can be seen from the above code, SpringServletContainerInitializer mainly defines this class and iteratively calls the onStartup () method of this class First of all, spring ServletContainerInitializer, as the implementation class of ServletContainerInitializer, will be called automatically when the web container is loaded through SPI mechanism. There is also an annotation @ HandlesTypes on this class, which is used to inject some interested classes into ServletContainerInitializer, In tomcat, you can get this annotation, take the SPI implementation class of ServletContainerInitializer as the key, and the value value of the annotation as a collection of values. You can see that the collection parameter passed from the onStartup() method of spring ServletContainerInitializer is the class injected by annotation scanning, and the method of this class will scan and find the implementation class of WebApplicationInitializer, Call its onStartup method to start the web XML has the same effect. In this way, we can do something through the SPI extension point when starting Tomcat So let's see what spring MVC does

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/*");
    }
}

As shown above, initialize the Spring life cycle and add the corresponding DispatcherServlet to the servletContext, so that when the request comes, the service() method of DispatcherServlet will be called So far, we have completed one of the two steps, adding dispatcher servlet to servletContext as Spring distributor The addServlet() method will not be posted. In fact, it is to create a StandardWrapper and call the wrapper Setservlet (servlet) combines servlets into wapper, which completes the container structure of tomcat In this way, when the request comes, the servlet will be obtained from the wapper for execution Next, we return to the original code and see the code shown below in the StandardContext

if (!listenerStart()) {
        log.error(sm.getString("standardContext.listenerFail"));
        ok = false;
}

public boolean listenerStart() {
    if (log.isDebugEnabled()) {
        log.debug("Configuring application event listeners");
    }
    String listeners[] = findApplicationListeners();
    Object results[] = new Object[listeners.length];
    boolean ok = true;
    for (int i = 0; i < results.length; i++) {
        if (getLogger().isDebugEnabled()) {
            getLogger().debug(" Configuring event listener class '" +
                listeners[i] + "'");
        }
        try {
            String listener = listeners[i];
            results[i] = getInstanceManager().newInstance(listener);
        } catch (Throwable t) {
            t = ExceptionUtils.unwrapInvocationTargetException(t);
            ExceptionUtils.handleThrowable(t);
            getLogger().error(sm.getString(
                    "standardContext.applicationListener", listeners[i]), t);
            ok = false;
        }
    }
    if (!ok) {
        getLogger().error(sm.getString("standardContext.applicationSkipped"));
        return false;
    }
    List<Object> eventListeners = new ArrayList<>();
    List<Object> lifecycleListeners = new ArrayList<>();
    for (Object result : results) {
        if ((result instanceof ServletContextAttributeListener)
                || (result instanceof ServletRequestAttributeListener)
                || (result instanceof ServletRequestListener)
                || (result instanceof HttpSessionIdListener)
                || (result instanceof HttpSessionAttributeListener)) {
            eventListeners.add(result);
        }
        if ((result instanceof ServletContextListener) || (result instanceof HttpSessionListener)) {
            lifecycleListeners.add(result);
        }
    }
    eventListeners.addAll(Arrays.asList(getApplicationEventListeners()));
    setApplicationEventListeners(eventListeners.toArray());
    for (Object lifecycleListener: getApplicationLifecycleListeners()) {
        lifecycleListeners.add(lifecycleListener);
        if (lifecycleListener instanceof ServletContextListener) {
            noPluggabilityListeners.add(lifecycleListener);
        }
    }
    setApplicationLifecycleListeners(lifecycleListeners.toArray());
    if (getLogger().isDebugEnabled()) {
        getLogger().debug("Sending application start events");
    }
    getServletContext();
    context.setNewServletContextListenerAllowed(false);
    Object instances[] = getApplicationLifecycleListeners();
    if (instances == null || instances.length == 0) {
        return ok;
    }
    ServletContextEvent event = new ServletContextEvent(getServletContext());
    ServletContextEvent tldEvent = null;
    if (noPluggabilityListeners.size() > 0) {
        noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
        tldEvent = new ServletContextEvent(noPluggabilityServletContext);
    }
    for (Object instance : instances) {
        if (!(instance instanceof ServletContextListener)) {
            continue;
        }
        ServletContextListener listener = (ServletContextListener) instance;
        try {
            fireContainerEvent("beforeContextInitialized", listener);
            if (noPluggabilityListeners.contains(listener)) {
                listener.contextInitialized(tldEvent);
            } else {
                listener.contextInitialized(event);
            }
            fireContainerEvent("afterContextInitialized", listener);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            fireContainerEvent("afterContextInitialized", listener);
            getLogger().error(sm.getString("standardContext.listenerStart",
                    instance.getClass().getName()), t);
            ok = false;
        }
    }
    return ok;
}

You can see that the code shown above is mainly obtained from all the implementation classes of ServletContextListener Then we call the contextInitialized method. Well, then we can take a look at our web XML has a listener class for context

Web.xml

<listener>
   	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

It's not hard to find the web There is a ContextLoaderListener class in the XML. This class will load the web when tomcat starts XML file and set it to ServletContextListener. The specific code is as follows

10.2 Spring life cycle

Let's take a look at the workflow of ContextLoaderListener, as shown in the figure below

As shown above, the work flow of the ContextLoaderListener class is shown in the sequence diagram above. It can be seen that the main method called is the refresh() method of the AbstractApplicationContext class, which can pull up the whole Spring life cycle process

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
   public ContextLoaderListener() {
   }
   public ContextLoaderListener(WebApplicationContext context) {
      super(context);
   }
   @Override
   public void contextInitialized(ServletContextEvent event) {
      initWebApplicationContext(event.getServletContext());
   }
   @Override
   public void contextDestroyed(ServletContextEvent event) {
      closeWebApplicationContext(event.getServletContext());
      ContextCleanupListener.cleanupAttributes(event.getServletContext());
   }
}

As shown in the above code, tomcat invokes the web.xml loaded when tomcat was started The ServletContextListener implementation class under the XML file calls the contextInitialized() method of ContextLoaderListener Well, let's see what this method does

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
      throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
            "check whether you have multiple ContextLoader* definitions in your web.xml!");
   }
   servletContext.log("Initializing Spring root WebApplicationContext");
   Log logger = LogFactory.getLog(ContextLoader.class);
   if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
   }
   long startTime = System.currentTimeMillis();
   try {
      if (this.context == null) {
         this.context = createWebApplicationContext(servletContext);
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         if (!cwac.isActive()) {
            if (cwac.getParent() == null) {
               ApplicationContext parent = loadParentContext(servletContext);
               cwac.setParent(parent);
            }
            configureAndRefreshWebApplicationContext(cwac, 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);
      }
      if (logger.isInfoEnabled()) {
         long elapsedTime = System.currentTimeMillis() - startTime;
         logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
      }
      return this.context;
   } catch (RuntimeException | Error ex) {
      logger.error("Context initialization failed", ex);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
   }
}

Initialize Spring's web application context according to the provided servlet context, and use the current application context during construction or on the web Configure the parameters contextClass and contextConfigLocation in XML to create a new context. First judge whether there is a root context in the ServletContext. If so, it indicates that it has been loaded or the configuration file has an error, which can be seen from the error message. Create a web application context through the createWebApplicationContext method. This context must implement the ConfigurableWebApplicationContext interface. Set the parent for root web application context and construct the creation of beans in the bean factory and container in the configureAndRefreshWebApplicationContext method. It will not be described here. We will study this topic next time, Finally, the following context is stored in the ServletContext, and the root web application context is stored in currentContextPerThread for subsequent retrieval of the current context. currentContextPerThread = new concurrenthashmap < classloader, webapplicationcontext > (1);.

The createWebApplicationContext method in the ContextLoader creates the root context

this.context = createWebApplicationContext(servletContext);

Let's mainly look at this line of core code to see what this code mainly does

protected WebApplicationContext createWebApplicationContext(ServletContext sc) { 
Class<?> contextClass = determineContextClass(sc);
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
            "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
   }
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

According to the initialization context, the final return value needs to be forcibly converted to ConfigurableWebApplicationContext. The determineContextClass method in the ContextLoader finds the Class type based on the context

protected Class<?> determineContextClass(ServletContext servletContext) {
   String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
   if (contextClassName != null) {
      try {
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }catch (ClassNotFoundException ex) {
         throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);
      }
   }else {
      contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
      try {
         return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
      }
      catch (ClassNotFoundException ex) {
         throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);
      }
   }
}

Web. If contextclass is configured in XML, its value will be taken, but it must implement ConfigurableWebApplicationContext. If not, the default value XmlWebApplicationContext will be taken. Contextclass defaults and contextloader Properties are as follows:

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }catch (IOException ex) {
      throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
   }
}

ContextLoader.properties

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

ContextLoader

if (this.context instanceof ConfigurableWebApplicationContext) {
   ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
   if (!cwac.isActive()) {
      if (cwac.getParent() == null) {
         ApplicationContext parent = loadParentContext(servletContext);
         cwac.setParent(parent);
      }
      configureAndRefreshWebApplicationContext(cwac, servletContext);
   }
}

configureAndRefreshWebApplicationContext(cwac, servletContext); The core code is as follows. You can see the following code: WAC refresh()

At this time, spring

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   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()));
      }
   }
   wac.setServletContext(sc);
   String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
   if (configLocationParam != null) {
      wac.setConfigLocation(configLocationParam);
   }
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
   }
   customizeContext(sc, wac);
   wac.refresh();
}

As shown in the above code, the whole tomcat and the whole spring MVC are started. In fact, we can also think that spring MVC - > spring boot is nothing more than configuration These two things still need to be done in the startup process, whether it is configured or not Then let's take a look at how Springboot and tomcat are integrated

Topics: Spring Tomcat Middleware