In traditional Java Web development, developers had to independently deploy Servlet containers, such as Tomcat, and put the application into a war package to run, which was more or less tedious and inconvenient to debug. The emergence of embedded Servlet containers has changed this situation. When using an embedded Servlet container, we no longer need any external support, and the application itself is a standalone entity. As a prime example of unlocking productivity, SpringBoot uses Embedded Tomcat by default to launch Web applications, which we’ll explore today (based on SpringBoot 2.3.0).

@SpringBootApplication
public class Application {
    public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

This code I believe you are familiar, it is the entrance to the application, everything starts from here. We directly follow up springApplication.run (…) And came to

public static ConfigurableApplicationContext run(Class
       [] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}
Copy the code

Static run (…). The function creates an instance of SpringApplication and calls its run(…). Method to follow up on the SpringApplication constructor

// The irrelevant logic has been removed
public SpringApplication(ResourceLoader resourceLoader, Class
       ... primarySources) {
    // Determine the current operating environment according to whether there is a corresponding class file in the classpath
    // We introduced spring-boot-starter-web, so the type is inferred to be SERVLET
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
}
Copy the code

Here we focus on how SpringBoot is concluded that the type of application, it is based on simple classpath to determine whether there is any specified class file, in our example type is inferred to WebApplicationType SERVLET. Then come run(…) Instance methods

// The irrelevant logic has been removed
public ConfigurableApplicationContext run(String... args) {
    ConfigurableApplicationContext context = null;
    try {
        1. Create ApplicationContext
        context = createApplicationContext();
        // refresh ApplicationContext
        refreshContext(context);
    }
    catch (Throwable ex) {
        throw new IllegalStateException(ex);
    }
    return context;
}
Copy the code

Leaving out irrelevant logic, run(…) The method mainly does two things:

  1. createApplicationContext
  2. The refreshApplicationContext

Huh? Embedded Tomcat is now up and running? It seems the secret lies in these two steps. View the source these two steps, it is easy to know is the specific type of the ApplicationContext AnnotationConfigServletWebServerApplicationContext, and refresh is called its refresh () method.

AnnotationConfigServletWebServerApplicationContext

To observe theAnnotationConfigServletWebServerApplicationContextThe inheritance tree, as you can see, outside the red circle is what we are very familiar with in the traditionWebFor use in the environmentApplicationContext; The part in the red circle, just by looking at the name can also be guessed is the focus of our study —WebServerApplicaitonContext.

A little further AnnotationConfigServletWebServerApplicationContext, it inherited from ServletWebServerApplicationContext, It also provides support for Component Scan and reading and parsing of @Configuration classes on the basis of the parent class.

public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
		implements AnnotationConfigRegistry {
	// Read and parse the @Configuration Configuration class
	private final AnnotatedBeanDefinitionReader reader;

    // Scan all components under the specified package
	private final ClassPathBeanDefinitionScanner scanner;
	
	// rest of codes are omitted...
}
Copy the code

This part of the function is implemented by the agent to AnnotatedBeanDefinitionReader and ClassPathBeanDefinitionScanner. These two brothers are also old friends and have nothing to do with the startup of the embedded Servlet container, so we won’t go into that.

WebServerApplicaitonContext

Next we focus falls on the WebServerApplicaitonContext, first take a look at the definition of it

/** * the implemented ApplicationContext is responsible for the creation and lifecycle management of the embedded WebServer.@link ApplicationContext application contexts} that
 * create and manage the lifecycle of an embedded {@link WebServer}.
 */
public interface WebServerApplicationContext extends ApplicationContext {
	/** * Returns the WebServer created by the current ApplicationContext and manages its lifecycle by returning a WebServer reference */
	WebServer getWebServer(a);

	/** * namespace, which can be used to avoid ambiguity when the application has multiple Webservers running */
	String getServerNamespace(a);

	static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {
		return (context instanceofWebServerApplicationContext) && ObjectUtils .nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace); }}// Subinterface, which can be configured to ApplicationContext
public interface ConfigurableWebServerApplicationContext extends ConfigurableApplicationContext.WebServerApplicationContext {
	/** * sets the namespace */
	void setServerNamespace(String serverNamespace);
}
Copy the code

Take a look at the definition of its associated WebServer

/** * represents a configured WebServer, * * Simple interface that represents the configuration of the web server (for example Tomcat, * Jetty, Netty). Allows the server to be {@link #start() started} and {@link #stop()
 * stopped}.
 */
public interface WebServer {
	/** * Start the server */
	void start(a) throws WebServerException;

	/** * Stop the server */
	void stop(a) throws WebServerException;

	/** * returns the port on which the server listens */
	int getPort(a);

	/** * gracefully close */
	default void shutDownGracefully(GracefulShutdownCallback callback) { callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE); }}Copy the code

WebServer is an abstraction of an embedded Servlet container, and it represents a fully configured Servlet container. In other words, the end user doesn’t need to care about the specifics of the container, just how to turn it on or off; And WebServerApplicationContext responsible for creating the WebServer, and as the end user at the right time to start or shut it down (that is, its lifecycle management).

ServletWebServerApplicationContext

ServletWebServerApplicationContext WebServerApplicationContext interface is achieved, and its creation and management for WebServer are concentrated in its own process, Namely ConfigurableApplicationContext# refresh () is called. Specifically, ServletWebServerApplicationContext fu wrote onRefresh (…). The hook method, which is called when:

  1. BeanFactoryInitialization complete
  2. BeanDefinitionParsing is complete
  3. Non-Lazy InitThe type ofBeanIt’s not initialized yet
@Override
protected void onRefresh(a) {
    super.onRefresh();
    try {
        / / create the WebServer
        createWebServer();
    } catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex); }}private void createWebServer(a) {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    // webServer == null: No webServer instance has been created
    // servletContext == null: no external container is used
    if (webServer == null && servletContext == null) {
        // 1. Get a factory to create WebServer
        ServletWebServerFactory factory = getWebServerFactory();
        // 2. Create a WebServer from the factory
        this.webServer = factory.getWebServer(getSelfInitializer());
        // 3. Listen for the lifecycle of ApplicationContext
        // Gracefully close WebServer on ApplicationContext#stop()
        getBeanFactory().registerSingleton("webServerGracefulShutdown".new WebServerGracefulShutdownLifecycle(this.webServer));
        // 4. Listen for the lifecycle of ApplicationContext
        // Start WebServer after all non-lazy Init beans have been initialized
        // Close WebServer on ApplicationContext#stop()
        getBeanFactory().registerSingleton("webServerStartStop".new WebServerStartStopLifecycle(this.this.webServer));
    }
    // External container
    else if(servletContext ! =null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        } catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex); }}/ / initialize ServletContextPropertySource
    // Expose the init-parameters of the ServletContext to the Environment
    initPropertySources();
}
Copy the code

The time to create a WebServer is before the Bean of type non-lazy Init is initialized by getting a unique ServletWebServerFactory in the BeanFactory. Note that getSelfInitializer() is an important parameter that we’ll get to later.

We then register two SmartLifecycle type components into the BeanFactory to manage the WebServer life cycle, one for gracefully shutting down the WebServer and one for starting or stopping the WebServer.

class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
	private final WebServer webServer;

	private volatile boolean running;

	WebServerGracefulShutdownLifecycle(WebServer webServer) {
		this.webServer = webServer;
	}

	@Override
	public void start(a) {
		this.running = true;
	}

	@Override
	public void stop(a) {
		throw new UnsupportedOperationException("Stop must not be invoked directly");
	}

	@Override
	public void stop(Runnable callback) {
        // Close webServer gracefully
		this.running = false;
		this.webServer.shutDownGracefully((result) -> callback.run());
	}

	@Override
	public boolean isRunning(a) {
		return this.running; }}class WebServerStartStopLifecycle implements SmartLifecycle {

	private final ServletWebServerApplicationContext applicationContext;

	private final WebServer webServer;

	private volatile boolean running;

	WebServerStartStopLifecycle(ServletWebServerApplicationContext applicationContext, WebServer webServer) {
		this.applicationContext = applicationContext;
		this.webServer = webServer;
	}

	@Override
	public void start(a) {
        / / start the webServer
		this.webServer.start();
		this.running = true;
		this.applicationContext.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
	}

	@Override
	public void stop(a) {
        / / close the webServer
		this.webServer.stop();
	}

	@Override
	public boolean isRunning(a) {
		return this.running;
	}

	@Override
	public int getPhase(a) {
        / / control of # stop () calls after WebServerGracefulShutdownLifecycle# stop (Runnable)
		return Integer.MAX_VALUE - 1; }}Copy the code

SmartLifecycle is the underlying component of spring-Context definition and is not the subject of this article. But to clarify the order of the calls, here is a brief description: It is driven by LifecycleProcessor, and ApplicationContext calls LifecycleProcessor#onRefresh() after all non-lazy Init beans have been initialized. And processing SmartLifecycle in it.

/ / the source code is located in the AbstractApplicationContext
protected void finishRefresh(a) {
    // Clear context-level resource caches (such as ASM metadata from scanning).
    clearResourceCaches();

    // Initialize LifecycleProcessor
    initLifecycleProcessor();

    / / callback LifecycleProcessor# onRefresh ()
    // Call SmartLifecycle#start() one by one in onRefresh()
    // There are, of course, some filtering conditions that we won't go into
    getLifecycleProcessor().onRefresh();

    / / release ContextRefreshedEvent
    publishEvent(new ContextRefreshedEvent(this));

    // Participate in LiveBeansView MBean, if active.
    LiveBeansView.registerApplicationContext(this);
}
Copy the code

This completes the analysis of how the embedded Servlet container is started.

ServletContextInitializer

ServletWebServerFactory mentioned in front, a parameter when creating WebServer — getSelfInitializer (), it is of type ServletContextInitializer.

public interface ServletContextInitializer {
   /** * Configure the ServletContext programmatically, such as registering servlets, filters, etc. */
   void onStartup(ServletContext servletContext) throws ServletException;
}
Copy the code

ServletContextInitializer role similar to ServletContainerInitializer, the latter is the Servlet API provides standard initializer. We can also to configure the ServletContext in ServletContextInitializer, the difference is that its life cycle by the BeanFactory management rather than the Servlet container.

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer(a) {
    return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
    ServletContext and ApplicationContext are bound to each other
    prepareWebApplicationContext(servletContext);
    // 2. Register ServletContextScope
    registerApplicationScope(servletContext);
    // 3. Register ServletContext and its associated init-parameters in BeanFactory
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 4. Key: Dynamically register servlets, filters and other components in ServletContext through ServletRegistrationBean, FilterRegistrationBean, etc
    for(ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); }}Copy the code

Step 1, step 2 and step 3 are relatively easy. Let’s take a look at step 4.

/ / initialization ServletContextInitializerBeans, it inherited from AbstractCollection
protected Collection<ServletContextInitializer> getServletContextInitializerBeans(a) {
    return new ServletContextInitializerBeans(getBeanFactory());
}

// constructor
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class<? extends ServletContextInitializer>... initializerTypes) {
   this.initializers = new LinkedMultiValueMap<>();
   this.initializerTypes = (initializerTypes.length ! =0)? Arrays.asList(initializerTypes) : Collections.singletonList(ServletContextInitializer.class);/ / all of the traverse the BeanFactory ServletContextInitializer
    // Handle ServletRegistrationBean, FilterRegistrationBean, etc
    // Finally add them into the initializers
   addServletContextInitializerBeans(beanFactory);
    // Iterate over the Servlet, Filter, and so on in the BeanFactory and wrap them into the corresponding RegistrationBean
    // Finally add them into the initializers
   addAdaptableBeans(beanFactory);
    // Order by the Ordered interface or @order annotation
   List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
         .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
         .collect(Collectors.toList());
    / / ServletContextInitializerBeans inherited from AbstractCollection, sortedList is its back to the list
   this.sortedList = Collections.unmodifiableList(sortedInitializers);
   logMappings(this.initializers);
}
Copy the code

ServletContextInitializerBeans at the time of initialization will retrieve all of the the BeanFactory RegistrationBean; If native Servlet, Filter, or Servlet Listener type beans still exist in the BeanFactory, they are wrapped into the corresponding RegistrationBeans, and finally all registrationBeans are sorted. Let’s use the ServletRegistrationBean to see how it implements adding servlets to the ServletContext.

As you can see from the inheritance tree,ServletRegistrationBeanIt’s also implementedServletContextInitializerTo check theonStartup(...)methods

// The source code is located at RegistrationBean
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
    // Get the description
    String description = getDescription();
    if(! isEnabled()) { logger.info(StringUtils.capitalize(description) +" was not registered (disabled)");
        return;
    }
    // Register with servletContext
    register(description, servletContext);
}
Copy the code

DynamicRegistrationBean implements register(…) methods

@Override
protected final void register(String description, ServletContext servletContext) {
    / / add the registration
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?) ");
        return;
    }
    Registration / / configuration
    configure(registration);
}
Copy the code

addRegistration(…) The final implementation is the ServletRegistrationBean

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
   String name = getServletName();
   return servletContext.addServlet(name, this.servlet);
}
Copy the code

But is directly using the ServletContext. AddServlet (…). A Servlet was dynamically added and nothing more.

Afterword.

We analyzed when the embedded Servlet container was created and started, not how it was created. Take Tomcat as an example. The corresponding TomcatWebServer encapsulates the Tomcat API and provides configuration for Connector, ErrorPage and other components. However, I am not familiar with the architecture of these containers, so I am not blind