)
SpringBoot built-in tomcat startup principle
preface
It has to be said that the developers of springboot are seeking benefits for the public program apes, and they are used to being lazy. xml is not configured, and even tomcat is lazily configured. A typical one click Startup System, so how does tomcat start in springboot?
Built in tomcat
For us in the development phase, using the built-in tomcat is very enough. Of course, jetty can also be used.
<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 sentences of code are SpringBootApplication annotation and springapplication Run() method.
Release production
At the time of release, most of the current practices are to exclude the built-in tomcat, war and deploy it in the production 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>
Update the main function, mainly inheriting the SpringBootServletInitializer and overriding 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 the 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. Here you can Doodle and replace it with the logo of your own project 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 context again 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 starts in spring boot, the run method focuses on creating application context and refreshContext.
Create context
//Create context protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch(this.webApplicationType) { case SERVLET: //Create annotationconfigservletwebserver ApplicationContext 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 will be created here.
The AnnotationConfigServletWebServerApplicationContext class inherits the ServletWebServerApplicationContext, and this class finally integrates AbstractApplicationContext.
Refresh context
//SpringApplication.java //Refresh context private void refreshContext(ConfigurableApplicationContext context) { this.refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException var3) { } } } //Here, the final parent class abstractapplicationcontext. Is called directly Refresh() method 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, return to the subclass: servletwebserver ApplicationContext, and call the onRefresh() method of this 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 method, I saw a familiar face, this Createwebserver, the mystery is about to be lifted. protected void onRefresh() { super.onRefresh(); try { this.createWebServer(); } catch (Throwable var2) { } } //ServletWebServerApplicationContext.java //Here is the creation of webServer, but tomcat has not been started. Here is the creation through ServletWebServerFactory. Next, look at ServletWebServerFactory 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
There are four implementation classes for the ServletWebServerFactory interface
There are two commonly used ones: TomcatServletWebServerFactory and JettyServletWebServerFactory.
//TomcatServletWebServerFactory.java //Here we use tomcat, so let's look at Tomcat servlet webserverfactory. I finally saw the trace 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 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 //Return to the engine container and see here. If you are familiar with the 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 a Tomcat object and does two important things: add the Connector object to tomcat, configureEngine(tomcat.getEngine());
The getWebServer method returns Tomcat webserver.
//TomcatWebServer.java //The constructor is called here to instantiate Tomcat webserver 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 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 tomcat service=== this.tomcat.start(); rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { } //Turn on non blocking Daemons 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 is the server Start will return to Tomcat webserver public void stop() throws LifecycleException { getServer(); server.stop(); }
//TomcatWebServer.java //Start 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; //Print this sentence on the console. 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()); } } } //Turn off 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 diagram of tomcat
The topmost 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 includes multiple connectors and a container. Connector is used to handle connection related matters and provide Socket to Request and Response related conversion. Container is used to encapsulate and manage servlets and handle specific requests. What about the Engine > host > context > wrapper container mentioned above? Let's look at the following figure:
To sum up, a tomcat contains only one Server. A Server can contain multiple services. A Service has only one Container but multiple connectors. Such a Service can handle multiple connections.
Multiple connectors and a Container form a Service. With a Service, you can provide services externally. However, in order to provide services, a Service must provide a hosting environment, which must belong to the Server. Therefore, the whole tomcat declaration cycle is controlled by the Server.
summary
The startup of SpringBoot is mainly started by instantiating SpringApplication. The startup process mainly does the following things: configuring properties, obtaining listeners, publishing application startup events, initializing input parameters, configuring environment, outputting banner, creating context, preprocessing context, refreshing context, refreshing context, publishing application startup events Publish application startup completion event. Starting tomcat in spring boot is the next step before refreshing. The startup of tomcat mainly instantiates 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, and a Container contains multiple sub containers.