Internal Tomcat startup mechanism of SpringBoot

preface

Have to say that the developers of SpringBoot is for the welfare of the public program ape, we are used to become lazy, XML configuration, even tomcat is lazy configuration, typical one-click boot system, so tomcat in SpringBoot is how to start?

Built-in tomcat

The development phase was sufficient for us to use the built-in Tomcat, but jetty could also be used.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <version>2.16..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)+"= = ="); }}Copy the code

Here is the entrance to the main function, and the two most eye-catching lines of code are the SpringBootApplication annotation and the SpringApplication.run() method.

Release the production

At the time of release, most of the current practice is to exclude the built-in Tomcat, make a war package and deploy it in production Tomcat. Well, what should be done when packaging?

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <! > <exclusions> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <! Javax. servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1. 0</version>
    <scope>provided</scope>
</dependency>
Copy the code

Update the main function, mainly inherited SpringBootServletInitializer, pay equal attention to write 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()); }}Copy the code

Start with the main function

public static ConfigurableApplicationContext run(Class
        primarySource, String... args) {
    return run(newClass[]{primarySource}, args); } -- where the run method returnsConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class
       [] primarySources, String[] args) {
	return (new SpringApplication(primarySources)).run(args);
}
Copy the code
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 the banner. Here you can doodle it and replace it with your own project logo
		Banner printedBanner = this.printBanner(environment);
		
		// Create the application context
		context = this.createApplicationContext();
		exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);

		// Preprocess the context
		this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		
		// Refresh the 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) {
		
	}
}
Copy the code

Since we want to know how tomcat is started in SpringBoot, we focus on creating the application context (createApplicationContext) and the refreshContext (refreshContext) in the run method.

Create context

// Create context
protected ConfigurableApplicationContext createApplicationContext(a) { Class<? > contextClass =this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch(this.webApplicationType) {
				case SERVLET:
                    / / create AnnotationConfigServletWebServerApplicationContext
				    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);
}
Copy the code

Here will create AnnotationConfigServletWebServerApplicationContext class. The inherited ServletWebServerApplicationContext AnnotationConfigServletWebServerApplicationContext class, And this class is finally integrated the AbstractApplicationContext.

Refresh context

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

/ / direct call the superclass AbstractApplicationContext finally here. The refresh () method
protected void refresh(ApplicationContext applicationContext) {
	((AbstractApplicationContext)applicationContext).refresh();
}
Copy the code
//AbstractApplicationContext.java
public void refresh(a) 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 each subclass onRefresh () method, which is said here to return to subclass: ServletWebServerApplicationContext, calling the class onRefresh () method
			this.onRefresh();
			this.registerListeners();
			this.finishBeanFactoryInitialization(beanFactory);
			this.finishRefresh();
		} catch (BeansException var9) {
			this.destroyBeans();
			this.cancelRefresh(var9);
			throw var9;
		} finally {
			this.resetCommonCaches(); }}}Copy the code
//ServletWebServerApplicationContext.java
This.createwebserver = this.createWebServer = this.createWebServer;
protected void onRefresh(a) {
	super.onRefresh();
	try {
		this.createWebServer();
	} catch (Throwable var2) {
		
	}
}

//ServletWebServerApplicationContext.java
// The webServer is created, but tomcat is not started yet, so the ServletWebServerFactory is created, so let's go to ServletWebServerFactory
private void createWebServer(a) {
	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);
}

/ / implementation
AbstractServletWebServerFactory
JettyServletWebServerFactory
TomcatServletWebServerFactory
UndertowServletWebServerFactory
Copy the code

There are four implementation classes for the ServletWebServerFactory interfaceAnd one of the two: TomcatServletWebServerFactory and JettyServletWebServerFactory.

//TomcatServletWebServerFactory.java
/ / here we use tomcat, so we see TomcatServletWebServerFactory. At last, there is a trace of Tomcat.
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
	Tomcat tomcat = new Tomcat();
	File baseDir = (this.baseDirectory ! = null) ?this.baseDirectory : createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
    // Create the Connector object
	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
If you are familiar with the Tomcat source code, you will be familiar with Engine.
public Engine getEngine(a) {
    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 a child of Engine, Context is a child of Host, and Wrapper is a child of Context
Copy the code

The getWebServer method creates the Tomcat object and does two important things: add the Connector object to Tomcat, configureEngine(tomcat.getengine ()); The getWebServer method returns TomcatWebServer.

//TomcatWebServer.java
// the constructor is called here to instantiate TomcatWebServer
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(a) throws WebServerException {
    // You will see this log on 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) {
                
			}
            
            // Enable blocking non-daemons
			startDaemonAwaitThread();
		}
		catch (Exception ex) {
			stopSilently();
			destroySilently();
			throw new WebServerException("Unable to start embedded Tomcat", ex); }}}Copy the code
//Tomcat.java
public void start(a) throws LifecycleException {
	getServer();
	server.start();
}
// server.start will return to TomcatWebServer
public void stop(a) throws LifecycleException {
	getServer();
	server.stop();
}
Copy the code
// tomcatwebserver. Java // Starting 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; 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()); @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(); }}}}Copy the code

Attached: Tomcat top-level structure diagram

The topmost container of Tomcat is Server, which represents the entire Server. A Server contains multiple services. From the figure above, it can be seen that there are multiple Connectors and one Container except Service. Connector handles connection-related issues and provides Socket to Request and Response conversions. Container is used to encapsulate and manage servlets and handle specific Request requests. What about the Engine>Host>Context>Wrapper container? Take a look at the picture below:To sum up, a Tomcat contains only one Server, and a Server can contain multiple services. A Service has only one Container and multiple connectors, so that a Service can handle multiple connections. Multiple Connectors and a Container form a Service. A Service can provide external services. However, a Service must provide a host environment to provide services. So the entire Tomcat declaration cycle is controlled by the Server.

conclusion

SpringBoot is started by instantiating the SpringApplication. The startup process does the following things: Configure properties, obtain listeners, publish the initial and initial input parameters of the application start event, configure the environment, output banner, create context, preprocess context, refresh context, refresh context, publish the event that the application has started, publish the event that the application has started. The job of starting Tomcat in SpringBoot is one step up from the refresh. The startup of Tomcat instantiates two components: A Tomcat instance is a Server. A Server contains multiple services, that is, multiple applications. Each Service contains multiple Connectors and a Container. A Container contains multiple subcontainers.

【 References 】