SpringBoot Built-in tomcat Startup Principle

Keywords: Java Tomcat Web Server Spring

Preface

SpringBoot developers have to say that they are working for the benefit of popular programming apes, making everyone used to being lazy, xml is not configured, even tomcat is lazy to configure, a typical one-button boot system, then how does tomcat start in spring boot?  

Built-in tomcat

The built-in tomcat is quite enough for us at the development stage, and of course jetty can be used as well.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <version>2.1.6.RELEASE</version>
</dependency>
@SpringBootApplication
public class MySpringbootTomcatStarter{
    public static void main(String[] args) {
        Long time=System.currentTimeMillis();
        SpringApplication.run(MySpringbootTomcatStarter.class);
        System.out.println("===Application startup time:"+(System.currentTimeMillis()-time)+"===");
    }
}

Here is the main function entry, the two most dazzling lines of code are the SpringBootApplication annotation and SpringApplication.run() method.

Publish production

At the time of release, most of the current practices excluded built-in tomcat, war and deployed in the production of tomcat, well, what should be done when packaging?

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- Remove Embedded tomcat Plug-in unit -->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--Add to servlet-api rely on--->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

Updating the main function mainly inherits SpringBootServletInitializer and overrides the configure() method.

@SpringBootApplication
public class MySpringbootTomcatStarter extends SpringBootServletInitializer {
    public static void main(String[] args) {
        Long time=System.currentTimeMillis();
        SpringApplication.run(MySpringbootTomcatStarter.class);
        System.out.println("===Application startup time:"+(System.currentTimeMillis()-time)+"===");
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }
}

Starting with main function

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}

--Here run Method returns ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}
public ConfigurableApplicationContext run(String... args) {
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        
        //Print banner, where you can graffiti for your own project logo
        Banner printedBanner = this.printBanner(environment);
        
        //Create application context
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);

        //Preprocessing context
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        //Refresh Context
        this.refreshContext(context);
        
        //Refresh the context
        this.afterRefresh(context, applicationArguments);
        
        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        
    }
}

Since we want to know how tomcat is started in SpringBook, the run method focuses on creating application context and refresh context.   

Create context

//Create context
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch(this.webApplicationType) {
                case SERVLET:
                    //Create Annotation Config Servlet Web Server Application Context
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException var3) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
        }
    }

    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

The AnnotationConfigServletWebServerApplicationContext class is created here.
The AnnotationConfigServletWebServerApplicationContext class inherits the ServletWebServerApplicationContext, which is the final integration of AbstractApplicationContext.

Refresh Context

//SpringApplication.java
//Refresh Context
private void refreshContext(ConfigurableApplicationContext context) {
    this.refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        } catch (AccessControlException var3) {
        }
    }
}

//The final parent AbstractApplicationContext.refresh() method is called directly here.
protected void refresh(ApplicationContext applicationContext) {
    ((AbstractApplicationContext)applicationContext).refresh();
}
//AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        this.prepareBeanFactory(beanFactory);

        try {
            this.postProcessBeanFactory(beanFactory);
            this.invokeBeanFactoryPostProcessors(beanFactory);
            this.registerBeanPostProcessors(beanFactory);
            this.initMessageSource();
            this.initApplicationEventMulticaster();
            //Call the onRefresh() method of each subclass, that is to say, go back to the subclass: ServletWebServerApplicationContext, and call the onRefresh() method of that class.
            this.onRefresh();
            this.registerListeners();
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
        } catch (BeansException var9) {
            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}
//ServletWebServerApplicationContext.java
//In this way, we can see the familiar face, this.createWebServer, and the mystery will be unveiled.
protected void onRefresh() {
    super.onRefresh();
    try {
        this.createWebServer();
    } catch (Throwable var2) {
        
    }
}

//ServletWebServerApplicationContext.java
//This is to create webServer, but tomcat has not been started yet. This is created through Servlet Web Server Factory. Then look at Servlet Web Server Factory.
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = this.getWebServerFactory();
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
    } else if (servletContext != null) {
        try {
            this.getSelfInitializer().onStartup(servletContext);
        } catch (ServletException var4) {
        
        }
    }

    this.initPropertySources();
}

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

//Realization
AbstractServletWebServerFactory
JettyServletWebServerFactory
TomcatServletWebServerFactory
UndertowServletWebServerFactory

Here, the Servlet Web ServerFactory interface has four implementation classes

There are two common ones: Tomcat Servlet Web Server Factory and Jetty Servlet Web Server Factory.

//TomcatServletWebServerFactory.java
//Here we use tomcat, so let's look at Tomcat Servlet Web Server Factory. At last I saw the traces of Tomcat here.
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    //Create Connector Objects
    Connector connector = new Connector(this.protocol);
    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);
}

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

//Tomcat.java
//Return to the Engine container and see that if you are familiar with tomcat source code, you will not be unfamiliar with Engine.
public Engine getEngine() {
    Service service = getServer().findServices()[0];
    if (service.getContainer() != null) {
        return service.getContainer();
    }
    Engine engine = new StandardEngine();
    engine.setName( "Tomcat" );
    engine.setDefaultHost(hostname);
    engine.setRealm(createDefaultRealm());
    service.setContainer(engine);
    return engine;
}
//Engine is the highest level container, Host is the sub-container of Engine, Context is the sub-container of Host and Wrapper is the sub-container of Context.

The getWebServer method creates Tomcat objects and does two important things: add Connector objects to Tomcat and configure Engine (tomcat. getEngine ());
The getWebServer method returns TomcatWebServer.

//TomcatWebServer.java
//This calls the constructor to instantiate Tomcat Web Server
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    initialize();
}

private void initialize() throws WebServerException {
    //You can see this log in the console
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();

            Context context = findContext();
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                    removeServiceConnectors();
                }
            });

            //=== Start the tomcat service===
            this.tomcat.start();

            rethrowDeferredStartupExceptions();

            try {
                ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
            }
            catch (NamingException ex) {
                
            }
            
            //Open blocking non-daemon processes
            startDaemonAwaitThread();
        }
        catch (Exception ex) {
            stopSilently();
            destroySilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
    }
}
//Tomcat.java
public void start() throws LifecycleException {
    getServer();
    server.start();
}
//Here server.start will return to Tomcat Web Server
public void stop() throws LifecycleException {
    getServer();
    server.stop();
}
//TomcatWebServer.java
//Start the tomcat service
@Override
public void start() throws WebServerException {
    synchronized (this.monitor) {
        if (this.started) {
            return;
        }
        try {
            addPreviouslyRemovedConnectors();
            Connector connector = this.tomcat.getConnector();
            if (connector != null && this.autoStart) {
                performDeferredLoadOnStartup();
            }
            checkThatConnectorsHaveStarted();
            this.started = true;
            //This log is printed on the console, and if the context is set in yml, it will be printed here.
            logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
                    + getContextPath() + "'");
        }
        catch (ConnectorStartFailedException ex) {
            stopSilently();
            throw ex;
        }
        catch (Exception ex) {
            throw new WebServerException("Unable to start embedded Tomcat server", ex);
        }
        finally {
            Context context = findContext();
            ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
        }
    }
}

//Close tomcat service
@Override
public void stop() throws WebServerException {
    synchronized (this.monitor) {
        boolean wasStarted = this.started;
        try {
            this.started = false;
            try {
                stopTomcat();
                this.tomcat.destroy();
            }
            catch (LifecycleException ex) {
                
            }
        }
        catch (Exception ex) {
            throw new WebServerException("Unable to stop embedded Tomcat", ex);
        }
        finally {
            if (wasStarted) {
                containerCounter.decrementAndGet();
            }
        }
    }
}

 

Attachment: Top-level structure of tomcat


The top-level container of tomcat is Server, which represents the whole server. A Server contains multiple services. As can be seen from the above figure, Service mainly consists of multiple Connectors and a Container. Connector is used to handle connection-related things and provide Socket-to-Request and Response-related transformations. Container is used to encapsulate and manage Servlet s and to process specific Request requests. So what about the Engine > Host > Context > Wrapper container mentioned above? Let's look at the following picture:

To sum up, a tomcat contains only one Server, a Server can contain multiple services, a Service has only one Container, but there are multiple Connector s, such a Service can handle multiple connections.
Multiple Connector s and a Container form a Service, which can provide services to the outside world, but Service must provide a host environment to provide services, which is not Server, so the whole declaration cycle of tomcat is controlled by Server.

summary

SpringBoot is started by instantiating Spring Application. The process of starting SpringBoot includes configuring attributes, acquiring listeners, publishing applications to start events, initializing input parameters, configuring environment, outputting banner, creating context, preprocessing context and refreshing context. Re-refresh the context, publish the event that the application has started, and publish the event that the application has started and completed. Starting tomcat in SpringBook is the next step in refreshing. The starting of tomcat is mainly to instantiate two components: Connector and Container. A tomcat instance is a Server. A Server contains multiple services, that is, multiple applications. Each Service contains multiple Connectors and a Container, while a Container contains multiple sub-containers.

Posted by mazzzzz on Tue, 20 Aug 2019 01:54:13 -0700