help me! On the first day of work, my boss asked me about the built-in SpringBoot. I don't know how to fix it?? SpringBoot built-in tomcat startup principle

Posted by ed01 on Sun, 23 Jan 2022 12:04:31 +0100


)

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.

[References]

Topics: Java Back-end