SpringBoot embedded Tomcat implementation principle analysis

For a SpringBoot Web project, a major dependency flag is the spring-boot-starter-web starter, The spring-boot-starter-web module has no code in Spring Boot. It only has some dependencies in POm. XML, including Web, webMVC, tomcat, etc. :

<dependencies>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-json</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.hibernate.validator</groupId>
    	<artifactId>hibernate-validator</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-web</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-webmvc</artifactId>
    </dependency>
</dependencies>
Copy the code

The default Spring Boot Web service container is Tomcat. If you want to use Jetty to replace Tomcat, you can refer to the official documentation to solve the problem.

Web, WebMVC, and Tomcat provide the running environment for Web applications. Spring-boot-starter is the switch that makes these environments work (because spring-boot-autoconfigure is indirectly introduced in spring-boot-starter).

WebServer automatic configuration

In the spring – the boot – autoconfigure module, have a handle on the WebServer automatic ServletWebServerFactoryAutoConfiguration configuration.

ServletWebServerFactoryAutoConfiguration

The code snippet is as follows:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration
Copy the code


The two conditions indicate that the current runtime environment is a Web service based on the Servlet standard specification:

  • ConditionalOnClass(servletrequest.class) : specifies that servlet-API dependencies must currently exist
  • ConditionalOnWebApplication (type = the SERVLET) : only SERVLET based Web applications

@ EnableConfigurationProperties (ServerProperties. Class) : ServerProperties configuration includes common server port configuration properties, etc.

Import the embedded container-related automatic configuration classes EmbeddedTomcat, EmbeddedJetty, and EmbeddedUndertow via @import.

Taken together, ServletWebServerFactoryAutoConfiguration automatic configuration class mainly do the following things:

  • Import the inner class BeanPostProcessorsRegistrar, it implements the ImportBeanDefinitionRegistrar, Can realize to register additional BeanDefinition ImportBeanDefinitionRegistrar.
  • Imported the ServletWebServerFactoryConfiguration. EmbeddedTomcat first close to embed containers, such as configuration, we focus on tomcat related configuration).
  • Registered ServletWebServerFactoryCustomizer, TomcatServletWebServerFactoryCustomizer WebServerFactoryCustomizer two types of beans.

The following is a detailed analysis for these points.

BeanPostProcessorsRegistrar

BeanPostProcessorsRegistrar the inner class code is as follows (omitted part of the code) :

public static class BeanPostProcessorsRegistrar
    implements ImportBeanDefinitionRegistrar.BeanFactoryAware {
    // omit the code
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (this.beanFactory == null) {
            return;
        }
        / / register WebServerFactoryCustomizerBeanPostProcessor
        registerSyntheticBeanIfMissing(registry,
                                       "webServerFactoryCustomizerBeanPostProcessor",
                                       WebServerFactoryCustomizerBeanPostProcessor.class);
        / / register errorPageRegistrarBeanPostProcessor
        registerSyntheticBeanIfMissing(registry,
                                       "errorPageRegistrarBeanPostProcessor",
                                       ErrorPageRegistrarBeanPostProcessor.class);
    }
    // omit the code
}
Copy the code

The code above, registered two beans, a WebServerFactoryCustomizerBeanPostProcessor, a errorPageRegistrarBeanPostProcessor; Both implement the BeanPostProcessor interface, which belongs to the bean’s post-processor and is used to add its own logic to the bean before and after initialization.

  • WebServerFactoryCustomizerBeanPostProcessor: Initialization is used in WebServerFactory calls when the WebServerFactoryCustomizer automatic injection configuration above, Then call the customize method to handle WebServerFactory WebServerFactoryCustomizer.
  • ErrorPageRegistrarBeanPostProcessor: similar to the role of the above, but this is processing ErrorPageRegistrar.

The following simple look at the code in the WebServerFactoryCustomizerBeanPostProcessor:

public class WebServerFactoryCustomizerBeanPostProcessor
		implements BeanPostProcessor.BeanFactoryAware {
    // Omit some code
    
    / / in postProcessBeforeInitialization approach, if the current bean is WebServerFactory, is performed
    // Some post-processing
    @Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		if (bean instanceof WebServerFactory) {
			postProcessBeforeInitialization((WebServerFactory) bean);
		}
		return bean;
	}
    // This code just takes all of the Customizers and loops through them to call the Customize method
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
		LambdaSafe
				.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
						webServerFactory)
				.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
				.invoke((customizer) -> customizer.customize(webServerFactory));
	}
    
    // Omit some code
}
Copy the code

The two Customizer beans registered in the auto-configure class

The two Customizers actually handle configuration values and bind them to their respective factory classes.

WebServerFactoryCustomizer

ServerProperties configuration values are bound to ConfigurableServletWebServerFactory object instance.

@Override
public void customize(ConfigurableServletWebServerFactory factory) {
    PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
    / / port
    map.from(this.serverProperties::getPort).to(factory::setPort);
    // address
    map.from(this.serverProperties::getAddress).to(factory::setAddress);
    // contextPath
    map.from(this.serverProperties.getServlet()::getContextPath)
        .to(factory::setContextPath);
    // displayName
    map.from(this.serverProperties.getServlet()::getApplicationDisplayName)
        .to(factory::setDisplayName);
    / / the session configuration
    map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
    // ssl
    map.from(this.serverProperties::getSsl).to(factory::setSsl);
    // jsp
    map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
    // Compress configuration policy implementation
    map.from(this.serverProperties::getCompression).to(factory::setCompression);
    // http2 
    map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
    // serverHeader
    map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
    // contextParameters
    map.from(this.serverProperties.getServlet()::getContextParameters)
        .to(factory::setInitParameters);
}
Copy the code

TomcatServletWebServerFactoryCustomizer

In contrast to the above, this Customizer deals mainly with Tomcat related configuration values

@Override
public void customize(TomcatServletWebServerFactory factory) {
    // Get the tomcat configuration
    ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
    // server.tomcat.additional-tld-skip-patterns
    if(! ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { factory.getTldSkipPatterns() .addAll(tomcatProperties.getAdditionalTldSkipPatterns()); }// server.redirectContextRoot
    if(tomcatProperties.getRedirectContextRoot() ! =null) {
        customizeRedirectContextRoot(factory,
                                     tomcatProperties.getRedirectContextRoot());
    }
    // server.useRelativeRedirects
    if(tomcatProperties.getUseRelativeRedirects() ! =null) { customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects()); }}Copy the code

WebServerFactory

The tag interface used to create the WebServer factory.

Class architecture

Whole structure of above WebServerFactory – > TomcatServletWebServerFactory class relations.

TomcatServletWebServerFactory

TomcatServletWebServerFactory is used to get the Tomcat as a WebServer factory class implements, is one of the most core method getWebServer, obtain a WebServer object instances.

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    // Create a Tomcat instance
    Tomcat tomcat = new Tomcat();
    // Create a Tomcat instance workspace directory
    File baseDir = (this.baseDirectory ! =null)?this.baseDirectory
        : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // Create a connection object
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    / / 1
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    // Configure Engine, no real operation, can be ignored
    configureEngine(tomcat.getEngine());
    // Some additional links, default is 0
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    / / 2
    prepareContext(tomcat.getHost(), initializers);
    / / returns the webServer
    return getTomcatWebServer(tomcat);
}
Copy the code
  • 1. CustomizeConnector: Set port, protocolHandler, and uriEncoding for Connector. The logic constructed by the Connector is primarily to select a protocol between the NIO and APR options, then create an instance and force a ProtocolHandler
  • Prepare a StandardContext, i.e. prepare a Web app.

Prepare the Web App Context container

For Tomcat, each context is mapped to a Web App, so what prepareContext does is map the Web application to a TomcatEmbeddedContext and add it to the Host.

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    // Create a TomcatEmbeddedContext object
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if(documentRoot ! =null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    // Sets the name string that describes the container. The container name must be unique within the set of child containers that belong to a particular parent item.
    context.setName(getContextPath());
    // Set the display name for this Web application.
    context.setDisplayName(getDisplayName());
    // Set webContextPath to/by defaultcontext.setPath(getContextPath()); File docBase = (documentRoot ! =null)? documentRoot : createTempDir("tomcat-docbase");
    context.setDocBase(docBase.getAbsolutePath());
    // Register a FixContextListener that sets the configuration state of the context and whether to add logon authentication logic
    context.addLifecycleListener(new FixContextListener());
    // Sets the parent ClassLoader
    context.setParentClassLoader(
        (this.resourceLoader ! =null)?this.resourceLoader.getClassLoader()
        : ClassUtils.getDefaultClassLoader());
    // Override Tomcat's default locale mapping to align with other servers.
    resetDefaultLocaleMapping(context);
    // Add locale encoding mapping (see section 5.4 of Servlet specification 2.4)
    addLocaleMappings(context);
    // Sets whether to use relative address redirection
    context.setUseRelativeRedirects(false);
    try {
        context.setCreateUploadTargets(true);
    }
    catch (NoSuchMethodError ex) {
        // Tomcat is < 8.5.39. Continue
    }
    configureTldSkipPatterns(context);
    // Set WebappLoader with the parent classLoader as the build parameter
    WebappLoader loader = new WebappLoader(context.getParentClassLoader());
    // Set the loaderClass value for WebappLoader
    loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
    // Delegate the loaded class up
    loader.setDelegate(true);
    context.setLoader(loader);
    if (isRegisterDefaultServlet()) {
        addDefaultServlet(context);
    }
    // Whether to register jspServlet
    if (shouldRegisterJspServlet()) {
        addJspServlet(context);
        addJasperInitializer(context);
    }
    context.addLifecycleListener(new StaticResourceConfigurer(context));
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    // Add a context container to host
    / / add to the context to register a memory leak tracking surveillance MemoryLeakTrackingListener, see the addChild method
    host.addChild(context);
    // Do some setup work on the context, including TomcatStarter(instantiate and set to the context),
    / / LifecycleListener contextValue, errorpage, Mime, session persistence, etc and some custom work overtime
    configureContext(context, initializersToUse);
    // The postProcessContext method is empty for subclass overrides
    postProcessContext(context);
}
Copy the code

WebappLoader can use setLoaderClass and getLoaderClass to change the value of loaderClass. This means that we can define a class that inherits webappClassLoader instead of the default implementation.

Initialize TomcatWebServer

At the end of the getWebServer method is to build a TomcatWebServer.

// org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    // New a TomcatWebServer
    return new TomcatWebServer(tomcat, getPort() >= 0);
}
// org.springframework.boot.web.embedded.tomcat.TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    / / initialization
    initialize();
}
Copy the code

The main one here is the initialize method, which will start the Tomcat service

private void initialize(a) throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            // For the global atomic variable containerCounter+1, since the initial value is -1,
    / / so addInstanceIdToEngineName methods follow-up for engine and set the name of the logic will not perform
            addInstanceIdToEngineName();
			/ / get the Context
            Context context = findContext();
            // Give Context an instance lifecycle listener
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource())
                    && Lifecycle.START_EVENT.equals(event.getType())) {
                    // Save the connection of the new above as service (in this case StandardService[Tomcat]) as key to
                    Connector.setservice ((service)null); .
                    // Start the container with LifecycleBase and the Connector will not be startedremoveServiceConnectors(); }});// Start the server to trigger listener initialization
            this.tomcat.start();
            // This method checks for exceptions during initialization. If any are thrown directly on the main thread,
            // The check method is TomcatStarter startUpException, which is recorded during the Context startup process
            rethrowDeferredStartupExceptions();
            try {
                // bind named context and classloader,
                ContextBindings.bindClassLoader(context, context.getNamingToken(),
                                                getClass().getClassLoader());
            }
            catch (NamingException ex) {
                // Setup failure is not a concern
            }

			// : Unlike Jetty, all Tomcat threads are daemon threads, so create a non-daemon thread
            // (e.g. Thread[container-0,5,main]) to prevent the service from being shutdown
            startDaemonAwaitThread();
        }
        catch (Exception ex) {
            stopSilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex); }}}Copy the code

SpringBoot has one Tomcat by default, and only one Web application in one Tomcat (FATJAR mode, application and Tomcat are 1: Container (context.context.context.context.context.context.context.context.context.context.context.context.context.context.context.context.context.context

private Context findContext(a) {
    for (Container child : this.tomcat.getHost().findChildren()) {
        if (child instanceof Context) {
            return(Context) child; }}throw new IllegalStateException("The host does not contain a Context");
}
Copy the code

Tomcat Startup Process

Tomcat is started in the Initialize method of TomcatWebServer.

// Start the server to trigger initialization listeners
this.tomcat.start();
Copy the code

Org. Apache. Catalina. Startup. Tomcat start method:

public void start(a) throws LifecycleException {
    // Initialize the server
    getServer();
    / / start the server
    server.start();
}
Copy the code

Initialize the Server

Initializing the server is essentially building an instance of the StandardServer object, as described in the appendix for server in Tomcat.

public Server getServer(a) {
	// Return it if it already exists
    if(server ! =null) {
        return server;
    }
	// Set system attributes catalina.usenaming
    System.setProperty("catalina.useNaming"."false");
	// Directly new a StandardServer
    server = new StandardServer();
	// Initialize baseDir (catalina.base, catalina.home, ~/tomcat.{port})
    initBaseDir();

    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

    server.setPort( -1 );

    Service service = new StandardService();
    service.setName("Tomcat");
    server.addService(service);
    return server;
}
Copy the code

summary

The process of embedding Tomcat in SpringBoot is actually not complicated. The process is to start the Tomcat container during the Spring Context refresh, bind the current application to a Context, and then add Host. The following figure shows the execution stack of the program and the timing of the initialization and startup of the embedded Tomcat.

Here’s a summary of the process:

  • The associated beans are registered through a custom configuration, including some factories, post-processors, and so on
  • The context refresh phase performs the creation of the WebServer, using the beans registered in the previous phase
    • This includes creating a ServletContext
    • Instantiate the webServer
  • Create a Tomcat instance and create a Connector
  • The binding applies to the ServletContext, adds listeners in the relevant lifecycle category, and then adds the Context to the host
  • Instantiate the webServer and start the Tomcat service

The Fatjar method of SpringBoot does not provide the implementation logic of shared Tomcat, that is, two FATJAT boot can only instantiate one Tomcat instance (including Connector and Host). As we know from the previous analysis, Each instance of a Web application (a FATJAT application) is mapped to a Context; With WAR, multiple contexts can be mounted to a Host.

Attached: Description of Tomcat components

Component name instructions
Server Represents the entire Servlet container, so there is only one Server instance in the Tomcat runtime environment
Service A Service represents a collection of one or more Connectors that share the same Container to handle its requests. You can have any number of Service instances within the same Tomcat instance, independent of each other.
Connector The Tomcat connector monitors and converts Socket requests, and sends the read Socket requests to Containers for processing. It supports different protocols and I/O modes.
Container Container represents an object that can execute client requests and return responses. Tomcat has different levels of containers: Engine, Host, Context, and Wrapper
Engine Engine represents the entire Servlet Engine. In Tomcat, Engine is the highest-level container object, and although Engine is not the container that directly processes requests, it is the entry point to the target container
Host Host, as a class container, represents the virtual machines in the Servlet Engine and is related to the network name of a server, such as the domain name. Clients can connect to the server using this network name, which must be registered on the DNS server
Context Context is a container that represents a ServletContext. In the Servlet specification, a ServletContext represents an independent Web application
Wrapper Wrapper is a type of container used to represent servlets defined in Web applications
Executor Represents the thread pool that can be shared between Tomcat components