How does Spring Boot start embedded Tomcat?

Posted by VenusJ on Tue, 04 Jan 2022 03:06:09 +0100

Spring Boot internally starts an embedded Web container.
Tomcat is a component-based design, so it is to start these components.

The Tomcat independent deployment mode is started through the startup script. The Bootstrap and Catalina in Tomcat are responsible for initializing the class loader and parsing the server XML and start these components.

In the embedded mode, Spring Boot does the work of Bootstrap and Catalina. Spring Boot calls Tomcat API to start these components.

Web container related interfaces in Spring Boot

WebServer

To support various Web containers, Spring Boot abstracts the embedded Web container and defines the WebServer interface:

Web containers such as Tomcat and Jetty implement the interface

ServletWebServerFactory

Create a Web container and return the WebServer mentioned above.

public interface ServletWebServerFactory {
    WebServer getWebServer(ServletContextInitializer... initializers);
}

The ServletContextInitializer input parameter represents the initializer of ServletContext, which is used for some configurations in ServletContext:

public interface ServletContextInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}

getWebServer will call ServletContextInitializer#onStartup, that is, if you want to do something when the Servlet container starts, such as registering your own Servlet, you can implement a ServletContextInitializer. When the Web container starts, Spring Boot will collect all classes that implement the ServletContextInitializer interface and call its onStartup uniformly.

WebServerFactoryCustomizerBeanPostProcessor

A BeanPostProcessor is used to customize the embedded Web container. In the process of postProcessBeforeInitialization, find the Bean of WebServerFactoryCustomizer type in the Spring container, and call the customize method of WebServerFactoryCustomizer interface in turn to make some customization.

public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
    void customize(T factory);
}

Create and start embedded Web container

Spring's ApplicationContext, its abstract implementation class AbstractApplicationContext#refresh

It is used to create or refresh an ApplicationContext. onRefresh will be called in refresh. Subclasses of AbstractApplicationContext can override onRefresh to implement Context refresh logic.

Therefore, rewrite servletwebserver applicationcontext#onrefresh to create an embedded Web container:

The create and create tomoncat server methods are overridden.

createWebServer

private void createWebServer() {
    // WebServer is an interface abstracted from Spring Boot, and the specific implementation classes are different Web containers
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    
    // If the Web container has not been created
    if (webServer == null && servletContext == null) {
        // Create through Web container factory
        ServletWebServerFactory factory = this.getWebServerFactory();
        // Pass in a 'SelfInitializer'
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        
    } else if (servletContext != null) {
        try {
            this.getSelfInitializer().onStartup(servletContext);
        } catch (ServletException var4) {
          ...
        }
    }

    this.initPropertySources();
}

getWebServer

Take Tomcat as an example. It mainly calls Tomcat's API to create various components:

public WebServer getWebServer(ServletContextInitializer... initializers) {
    // 1. Instantiate a Tomcat [Server component]
    Tomcat tomcat = new Tomcat();
    
    // 2. Create a temporary directory
    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    
    // 3. Initialize various components
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    this.customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    this.configureEngine(tomcat.getEngine());
    
    // 4. Create a customized "Context" component
    this.prepareContext(tomcat.getHost(), initializers);
    return this.getTomcatWebServer(tomcat);
}

The Context of prepareContext refers to the Context component of Tomcat. In order to control the behavior of the Context component, Spring Boot customizes the TomcatEmbeddedContext class and inherits the standard Context of Tomcat:

Register Servlet

  • With @ RestController, why register the Servlet for Tomcat?
    In some scenarios, you may need to register a Servlet written by yourself to provide auxiliary functions, which is separate from the main program.

  • Why doesn't spring boot register a Servlet to Tomcat and directly use @ Controller to realize the Servlet function?
    Because the sprang boot registers the dispatcher setvlet for us by default.

Servlet annotation

After the @ ServletComponentScan annotation is added to the Spring Boot class, the servlets, filters and listeners marked with @ WebServlet, @ WebFilter and @ WebListener can be automatically registered to the Servlet container.


Add @ ServletComponentScan to the entry class of the Web application and @ WebServlet to the Servlet class, so that Spring Boot will be responsible for registering the Servlet to the embedded Tomcat.

ServletRegistrationBean

Spring Boot provides

  • ServletRegistrationBean
  • FilterRegistrationBean
  • ServletListenerRegistrationBean

Used to register Servlet, Filter and Listener respectively.
To register a Servlet:

Return a ServletRegistrationBean and register it with Spring as a Bean, so you need to put this code in the directory automatically scanned by Spring Boot or in the class identified by * * @ Configuration * *.
Spring will collect this type of beans and register servlets with Tomcat according to the definitions in the beans.

Dynamic registration

You can create a class to implement the ServletContextInitializer interface and register it as a Bean. Spring Boot will be responsible for calling onStartup of this interface.

Classes that implement the ServletContextInitializer interface are managed by spring, not by the Servlet container.

@Component
public class MyServletRegister implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
    
        // Servlet 3.0 specification new API
        ServletRegistration myServlet = servletContext
                .addServlet("HelloServlet", HelloServlet.class);
                
        myServlet.addMapping("/hello");
        
        myServlet.setInitParameter("name", "Hello Servlet");
    }

}

ServletRegistrationBean is also implemented through ServletContextInitializer, which implements the ServletContextInitializer interface.
Note that the parameter of onStartup method is the familiar ServletContext. You can dynamically register new servlets by calling its addServlet method, which is a function only after Servlet 3.0.

The Servlet can be registered with the Web container through the ServletContextInitializer interface. The Bean implementing the ServletContextInitializer interface is managed by speed, but when will its onStartup() method be triggered?
Through the implementer of ServletContainerInitializer interface in tomcat, such as TomcatStarter, this class is set when creating Tomcat. When Tomcat starts, the onStartup() method of ServletContainerInitializer implementer will be triggered. In this method, the onStartup() method of ServletContextInitializer interface will be triggered, such as registering DispatcherServlet.

DispatcherServletRegistrationBean implements the ServletContextInitializer interface. Its function is to register DispatcherServlet with Tomcat. When and how is it used?
The prepareContext method calls another private method configureContext, which includes adding a ServletContainerInitializer object to the Tomcat Context:

context.addServletContainerInitializer(starter, NO_CLASSES);

There are DispatcherServletRegistrationBean.

Custom Web container

How to customize the Web container in Spring Boot. In Spring Boot 2.0, you can:

ConfigurableServletWebServerFactory

General Web container factory, custom Web container general parameters:

@Component
public class MyGeneralCustomizer implements
  WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
  
    public void customize(ConfigurableServletWebServerFactory factory) {
        factory.setPort(8081);
        factory.setContextPath("/hello");
     }
}

TomcatServletWebServerFactory

Further customization through a specific Web container factory.

Add a Valve to Tomcat. The function of this Valve is to add traceid to the request header for distributed tracking.

class TraceValve extends ValveBase {
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {

        request.getCoyoteRequest().getMimeHeaders().
        addValue("traceid").setString("1234xxxxabcd");

        Valve next = getNext();
        if (null == next) {
            return;
        }

        next.invoke(request, response);
    }

}

Similar to method 1, add another Customization:

@Component
public class MyTomcatCustomizer implements
        WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.setPort(8081);
        factory.setContextPath("/hello");
        factory.addEngineValves(new TraceValve() );

    }
}

Topics: Tomcat