Servlet container and Web application

Wrong understanding of starting sequence

There has been a point before that the application runs in the Servlet container, because from the perspective of the usage of Servlet container and Web application, it really has this feeling.

We start the Servlet container every time, and then start our application. For example, if the Web application uses the Spring framework, we start the Servlet container first, and then the initialization of the Spring container.

In this way, there will be an illusion that the program code we write belongs to the runtime Servlet container, and the word container has deepened this misunderstanding.

Then we encounter the embedded Servlet container of Spring boot. In this case, we first initialize our Spring container. During the process of initializing SpringContext, we start our Servlet container.

This is embarrassing. It overturns the previous cognition. So I have a look at the process of Spring launching Servlet container and re understand the Servlet container.

Servlet container before Spring container

Before we used Servlet container to deploy Java Web application, we need to do the following configuration in web.xml

<!-- To configure ServletContext parameter -->
<context-param>
   <!-- Parameter name. This is fixed and cannot be changed -->
   <param-name>contextConfigLocation</param-name>
   <param-value>
     	 <!-- Spring Profile path -->
       classpath:applicationContext.xml
   </param-value>
</context-param>
<!-- Listener of context loader -->
<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Make the above configuration in web.xml. After the Servlet container is started successfully, our Spring ApplicationContext can be initialized

How do you do it? Take a little note

First, after the Servlet container is started, the configured listener will publish an event to all configured listeners and some listeners inside the Servlet container.

org.springframework.web.context.ContextLoaderListener implements ServletContextListener
public class ContextLoaderListener implements ServletContextListener {
    private ContextLoader contextLoader;

    public ContextLoaderListener() {
    }

    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = this.createContextLoader();
        // Get the ServletContext object from the ServletContextEvent event event
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

    protected ContextLoader createContextLoader() {
        return new ContextLoader();
    }

    public ContextLoader getContextLoader() {
        return this.contextLoader;
    }

    public void contextDestroyed(ServletContextEvent event) {
        if (this.contextLoader != null) {
            this.contextLoader.closeWebApplicationContext(event.getServletContext());
        }

    }
}

Then take a look at initializing WebApplicationContext

In the org.springframework.web.context.contextloader ා createwebapplicationcontext method, we can see the following content

protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) throws BeansException {
        Class contextClass = this.determineContextClass(servletContext);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("xxxx");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setParent(parent);
            wac.setServletContext(servletContext);
          	// Here is to get the Spring context configuration file path we configured from the ServletContext object
            wac.setConfigLocation(servletContext.getInitParameter("contextConfigLocation"));
            this.customizeContext(servletContext, wac);
            wac.refresh();
            return wac;
        }
    }

Through the above two classes, one configuration, we have a more intuitive understanding of using Servlet container to start Spring container.

Spring container before Servlet container

Next let's see how SpringBoot starts the Servlet container

This is generally the case when we start SpringBoot

SpringApplication.run(Application.class, args);

In the static run method, instantiate the SpringApplication object

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // Determine WebApplication type
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) 		        getSpringFactoriesInstances(ApplicationContextInitializer.class));    
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
static WebApplicationType deduceFromClasspath() {
  // If the class org.springframework.web.reactive.DispatcherHandler exists  
  // And there is no org.springframework.web.servlet.DispatcherServlet 
  // And org.glassfish.jersey.servlet.ServletContainer 
  // It is considered as a WEB application of REACTIVE type
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
  // javax.servlet.Servlet exists 
  // And org.springframework.web.context.ConfigurableWebApplicationContext 
  // It is considered as a WEB application of SERVLET type
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
        // It's not a WEB application if it doesn't show up
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

Then in the method org. Springframework. Boot. Springapplication? Createapplicationcontext

protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
      // If you haven't decided which subclass of ApplicationContext to use, it depends on WebApplicationType
			try {
				switch (this.webApplicationType) {
				case SERVLET:
          // Load the AnnotationConfigServletWebServerApplicationContext class
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
  	// Instantiate ApplicationContext object 
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

Then, in the org.springframework.boot.SpringApplication#run(java.lang.String...) method, call `org.springframework.boot.SpringApplication#refreshContext and call the following method.

protected void refresh(ApplicationContext applicationContext) {
    // Type judgment
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    // To call the refresh method, we use polymorphism to call the refresh method of the actual object 
		((AbstractApplicationContext) applicationContext).refresh();
}

Finally, it will call org.springframework.context.support.abstractapplicationcontext "Refresh"

There is an onRefresh() in the refresh method

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// ... omitted
			try {
				// ... omitted
				// Initialize other special beans in specific context subclasses.
        // In a specific context subclass, initialize some special beans
				onRefresh();
			  // ... omitted
			}
			catch (BeansException ex) {
				// ... omitted
			}
			finally {
				// ... omitted
			}
		}
	}

This onRefresh method is implemented by a subclass. Here is org.springframework.boot.web.servlet.context.servletwebserverbapplicationcontext

protected void onRefresh() {
		super.onRefresh();
		try {
      // The critical moment is here to create WebServer
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

You can see here for the moment. The next step is to start the operation according to which Servlet container's jar package is introduced. Take Tomcat as an example

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

After this step, the Servlet container is basically started, but the Spring container has not been initialized.

summary

It doesn't matter whether the Servlet container starts first or the Spring container starts first. The difference is the order.

These two constitute a whole, not a relationship between you and me, or between me and you.

When the Servlet container is started, or when the Spring container is started, a virtual machine instance process will be started. The code loaded later is all located in this virtual machine process. The Servlet container will be responsible for listening to a port, processing HTTP requests, and docking with our Spring container.

The starting sequence of the two methods does not change the processing flow of HTTP request.

It can also be seen that the two are independent of each other.

Tags: Java Spring Tomcat xml

Posted on Sun, 26 Apr 2020 02:10:55 -0700 by plautzer