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 】