How to initialize Tomcat in SpringBoot
preface
The creation of spring boot simplifies our work of creating and running projects. We don't have to package the project into a jar package, then put it into Tomcat and start it again, as in the spring MVC era. We can ignore the packaged operation and tomat configuration, and start the project directly after writing the code. All this benefits from the implementation of spring boot's built-in container. So, how does SpringBoot accomplish this series of operations of initializing Tomcat for us? Next, I will lead you to happily analyze the source code of SpringBoot from the perspective of first person learning source code
Source code entry
main methodSpring boot projects start from the start of the run method in the Application, so the initialization of Tomcat must also be in it.
public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); System.out.println("Demo Application is Running!"); }
Click the source code of the run method to view it
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<springbootexceptionreporter> exceptionReporters = new ArrayList<>(); //Set the system properties java.awt.headless If true, headless mode support is enabled configureHeadlessProperty(); //Retrieve * meta-inf through * SpringFactoriesLoader */ spring.factories *, //Find all the declared implementation classes of SpringApplicationRunListener and instantiate them, //Then call its started() method one by one, and the broadcast SpringBoot will start to execute SpringApplicationRunListeners listeners = getRunListeners(args); //Publish app start event listeners.starting(); try { //Initialization parameters ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //Create and configure the Environment (including PropertySource and Profile) to be used by the current SpringBoot application, //And traverse and call the environmentPrepared() method of all SpringApplicationRunListener, and the broadcast Environment is ready. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); //Print banner Banner printedBanner = printBanner(environment); //Create application context context = createApplicationContext(); //Retrieve * meta-inf through * SpringFactoriesLoader */ spring.factories *, get and instantiate the exception parser exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //Load the environment for ApplicationContext, and then execute the initialize() method of ApplicationContextInitializer one by one to further encapsulate ApplicationContext, //And call the contextPrepared() method of all springapplicationrunlisteners, [EventPublishingRunListener only provides an empty contextPrepared() method], //After that, initialize the IoC container, call the contextLoaded() method of SpringApplicationRunListener, and broadcast the IoC loading of ApplicationContext, //This includes various automatic configuration classes imported through * * @ EnableAutoConfiguration * *. prepareContext(context, environment, listeners, applicationArguments, printedBanner); //Refresh context refreshContext(context); //Refreshing the context again is actually an empty method, which may be for subsequent extension. afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } //Publish events that the app has started listeners.started(context); //Traverse all registered ApplicationRunner and CommandLineRunner and execute their run() method. //We can implement our own application runner or command line runner to extend the spring boot startup process. callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { //Listening event for application started and completed listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
Here, we can summarize the operation logic of the run method as follows:
1. Configure system properties
2. Get the listener of the spring application and publish the start event of the application
3. Initialize application startup parameters
4. Configure the application startup environment and output banner
5. Create context (including tomcat initialization)
6. Preprocessing context
7. Refresh context (including tomcat initialization)
8. Refresh the context again, and reserve the operation for subsequent extension. It can also be called empty refresh.
9. Listening events triggered when publishing an app
10. Listening event for publishing application start completion
createApplicationContext methodAmong them, steps 5 and 7 are important steps for us to study how to initialize tomcat. Next, let's take a look at the source code of these two methods and what they have done:
/** * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context or application context * class before falling back to a suitable default. * @return the application context (not yet refreshed) * @see #setApplicationContextClass(Class) */ protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { // Determine which type of servlet your project should create switch (this.webApplicationType) { // web type case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; // Non web type case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; // Default type default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
In the createApplicationContext method, we can roughly know that the main purpose here is to define the configuration type of the application. Are we creating a web type (SERVLET)? Non web type (REACTIVE)? Default type?
Since we are studying tomcat, we must start the application of web type. So, what we create through reflection here must be DEFAULT_SERVLET_WEB_CONTEXT_CLASS.
AnnotationConfigServletWebServerApplicationContext classAbove, default_ SERVLET_ WEB_ CONTEXT_ The class corresponding to the constant class is:
/** * The class name of application context that will be used by default for web * environments. */ public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
So, let's take a look at this class:
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext implements AnnotationConfigRegistry {...}
In this class, I don't want to deal with other issues, mainly because I see the inherited parent class, ServletWebServerApplicationContext.
ServletWebServerApplicationContext classFrom the name, we can see the function of this class - the context class of web type service application. So let's look at this class:
/* * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.web.servlet.context; import java.util.Collection; import java.util.Collections; import java.util.EventListener; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.Scope; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.ServletContextInitializerBeans; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.ServletContextAware; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.context.support.ServletContextAwareProcessor; import org.springframework.web.context.support.ServletContextResource; import org.springframework.web.context.support.ServletContextScope; import org.springframework.web.context.support.WebApplicationContextUtils; /** * A {@link WebApplicationContext} that can be used to bootstrap itself from a contained * {@link ServletWebServerFactory} bean. * <p> * This context will create, initialize and run an {@link WebServer} by searching for a * single {@link ServletWebServerFactory} bean within the {@link ApplicationContext} * itself. The {@link ServletWebServerFactory} is free to use standard Spring concepts * (such as dependency injection, lifecycle callbacks and property placeholder variables). * <p> * In addition, any {@link Servlet} or {@link Filter} beans defined in the context will be * automatically registered with the web server. In the case of a single Servlet bean, the * '/' mapping will be used. If multiple Servlet beans are found then the lowercase bean * name will be used as a mapping prefix. Any Servlet named 'dispatcherServlet' will * always be mapped to '/'. Filter beans will be mapped to all URLs ('/*'). * <p> * For more advanced configuration, the context can instead define beans that implement * the {@link ServletContextInitializer} interface (most often * {@link ServletRegistrationBean}s and/or {@link FilterRegistrationBean}s). To prevent * double registration, the use of {@link ServletContextInitializer} beans will disable * automatic Servlet and Filter bean registration. * <p> * Although this context can be used directly, most developers should consider using the * {@link AnnotationConfigServletWebServerApplicationContext} or * {@link XmlServletWebServerApplicationContext} variants. * * @author Phillip Webb * @author Dave Syer * @author Scott Frederick * @since 2.0.0 * @see AnnotationConfigServletWebServerApplicationContext * @see XmlServletWebServerApplicationContext * @see ServletWebServerFactory */ public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { private static final Log logger = LogFactory.getLog(ServletWebServerApplicationContext.class); /** * Constant value for the DispatcherServlet bean name. A Servlet bean with this name * is deemed to be the "main" servlet and is automatically given a mapping of "/" by * default. To change the default behavior you can use a * {@link ServletRegistrationBean} or a different bean name. */ public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet"; private volatile WebServer webServer; private ServletConfig servletConfig; private String serverNamespace; /** * Create a new {@link ServletWebServerApplicationContext}. */ public ServletWebServerApplicationContext() { } /** * Create a new {@link ServletWebServerApplicationContext} with the given * {@code DefaultListableBeanFactory}. * @param beanFactory the DefaultListableBeanFactory instance to use for this context */ public ServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) { super(beanFactory); } /** * Register ServletContextAwareProcessor. * @see ServletContextAwareProcessor */ @Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); registerWebApplicationScopes(); } @Override public final void refresh() throws BeansException, IllegalStateException { try { super.refresh(); } catch (RuntimeException ex) { stopAndReleaseWebServer(); throw ex; } } @Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } @Override protected void finishRefresh() { super.finishRefresh(); WebServer webServer = startWebServer(); if (webServer != null) { publishEvent(new ServletWebServerInitializedEvent(webServer, this)); } } @Override protected void onClose() { super.onClose(); stopAndReleaseWebServer(); } private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); } /** * Returns the {@link ServletWebServerFactory} that should be used to create the * embedded {@link WebServer}. By default this method searches for a suitable bean in * the context itself. * @return a {@link ServletWebServerFactory} (never {@code null}) */ protected ServletWebServerFactory getWebServerFactory() { // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean."); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); } /** * Returns the {@link ServletContextInitializer} that will be used to complete the * setup of this {@link WebApplicationContext}. * @return the self initializer * @see #prepareWebApplicationContext(ServletContext) */ private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } private void registerApplicationScope(ServletContext servletContext) { ServletContextScope appScope = new ServletContextScope(servletContext); getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. servletContext.setAttribute(ServletContextScope.class.getName(), appScope); } private void registerWebApplicationScopes() { ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(getBeanFactory()); WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory()); existingScopes.restore(); } /** * Returns {@link ServletContextInitializer}s that should be used with the embedded * web server. By default this method will first attempt to find * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain * {@link EventListener} beans. * @return the servlet initializer beans */ protected Collection<ServletContextInitializer> getServletContextInitializerBeans() { return new ServletContextInitializerBeans(getBeanFactory()); } /** * Prepare the {@link WebApplicationContext} with the given fully loaded * {@link ServletContext}. This method is usually called from * {@link ServletContextInitializer#onStartup(ServletContext)} and is similar to the * functionality usually provided by a {@link ContextLoaderListener}. * @param servletContext the operational servlet context */ protected void prepareWebApplicationContext(ServletContext servletContext) { Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (rootContext != null) { if (rootContext == this) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ServletContextInitializers!"); } return; } servletContext.log("Initializing Spring embedded WebApplicationContext"); try { servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this); if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } setServletContext(servletContext); if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - getStartupDate(); logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } } private WebServer startWebServer() { WebServer webServer = this.webServer; if (webServer != null) { webServer.start(); } return webServer; } private void stopAndReleaseWebServer() { WebServer webServer = this.webServer; if (webServer != null) { try { webServer.stop(); this.webServer = null; } catch (Exception ex) { throw new IllegalStateException(ex); } } } @Override protected Resource getResourceByPath(String path) { if (getServletContext() == null) { return new ClassPathContextResource(path, getClassLoader()); } return new ServletContextResource(getServletContext(), path); } @Override public String getServerNamespace() { return this.serverNamespace; } @Override public void setServerNamespace(String serverNamespace) { this.serverNamespace = serverNamespace; } @Override public void setServletConfig(ServletConfig servletConfig) { this.servletConfig = servletConfig; } @Override public ServletConfig getServletConfig() { return this.servletConfig; } /** * Returns the {@link WebServer} that was created by the context or {@code null} if * the server has not yet been created. * @return the embedded web server */ @Override public WebServer getWebServer() { return this.webServer; } /** * Utility class to store and restore any user defined scopes. This allow scopes to be * registered in an ApplicationContextInitializer in the same way as they would in a * classic non-embedded web application context. */ public static class ExistingWebApplicationScopes { private static final Set<String> SCOPES; static { Set<String> scopes = new LinkedHashSet<>(); scopes.add(WebApplicationContext.SCOPE_REQUEST); scopes.add(WebApplicationContext.SCOPE_SESSION); SCOPES = Collections.unmodifiableSet(scopes); } private final ConfigurableListableBeanFactory beanFactory; private final Map<String, Scope> scopes = new HashMap<>(); public ExistingWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { this.beanFactory = beanFactory; for (String scopeName : SCOPES) { Scope scope = beanFactory.getRegisteredScope(scopeName); if (scope != null) { this.scopes.put(scopeName, scope); } } } public void restore() { this.scopes.forEach((key, value) -> { if (logger.isInfoEnabled()) { logger.info("Restoring user defined scope " + key); } this.beanFactory.registerScope(key, value); }); } } }
In this class, it is nothing more than some methods for the whole web application life cycle operation. However, there is one method that stands out: the createWebServer () method.
Create webserver methodBy name, the purpose of this method is to create a web type application. So what is the truth? Let's take a look:
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { // Create an instance of webServer through factory mode ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
Throughout the whole method, we find the place to create web application instances. However, still did not find where to start tomcat, pit ah!!! However, we know that it creates an instance object of webServer through ServletWebServerFactory. Looking back at the ServletWebServerApplicationContext class, since we have an application instance object, is it time to start the instance object next? Let's find out whether there is a startup method in the next servlet web server application context.
startWebServer methodAs expected, there is really a way to start webServer. Let's see what this method is doing:
private WebServer startWebServer() { WebServer webServer = this.webServer; if (webServer != null) { webServer.start(); } return webServer; }
It's embarrassing. After getting the instance object created above directly, the start() method in WebServer is called directly to start after the judgment is not empty. Let's make a breakpoint here to debug the code and see what it looks like after running here:
Connected to the target VM, address: '127.0.0.1:59767', transport: 'socket' . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.8.RELEASE) 2020-06-28 17:03:07.041 INFO 4687 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on fanyuanhangdeMacBook-Pro.local with PID 4687 (/Users/fanyuanhang/Downloads/springboot/target/classes started by fanyuanhang in /Users/fanyuanhang/Downloads/springboot) 2020-06-28 17:03:07.044 INFO 4687 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default 2020-06-28 17:03:08.040 INFO 4687 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2020-06-28 17:03:08.049 INFO 4687 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2020-06-28 17:03:08.049 INFO 4687 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.36] 2020-06-28 17:03:08.120 INFO 4687 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2020-06-28 17:03:08.120 INFO 4687 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1025 ms 2020-06-28 17:03:08.302 INFO 4687 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2020-06-28 17:03:08.484 INFO 4687 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2020-06-28 17:03:28.064 INFO 4687 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 21.385 seconds (JVM running for 22.101)
I'll go!!! Just after running this method, tomcat is started and the default port 8080 is configured Speechless, it seems that tomcat configuration is completed before webServer is started
So, where is tomcat initialized? Is there a method like createtocatxxx() in servletwebserver ApplicationContext? Let's search:
GG!!! Muyou!!! It seems that the spring boot production group has hidden enough about tomcat initialization So let's calm down and analyze:
1. tomcat has been initialized before startup
2. There is no initialization method in servlet webserver ApplicationContext
Along this line of thinking, we think about what we would do as a producer: according to my habit of writing code, when creating an instance, I will configure all the necessary configurations of this instance when creating an instance. Do you? Did you want to go with me? Let's review the method of create web server again.
Looking back on the createWebServer methodprivate void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
Let's take a closer look at the create web server method again. Get the webServer instance object through the getWebServer () method provided by the factory interface ServletWebServerFactory em, it seems necessary to see how the implementation class of this interface is implemented. In the dark, I feel that I am a step closer to the truth, ha ha.
ServletWebServerFactory class diagram structureCall up the class diagram structure of this interface to:
em, your products, your fine products. There are three final implementation subclasses of the servlet webserverfactory interface, which respectively correspond to the configuration of the corresponding application type obtained according to the type in the first createApplicationContext method.
Don't even think about it here. The target is TomcatServletWebServerFactory. Let's see how to implement the getWebServer method here.
TomcatServletWebServerFactory class@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } // Create tomcat instance Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); customizeConnector(connector); // The tomcat setting connector will be created tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); // Configure engine configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); // Start tomcat return getTomcatWebServer(tomcat); }
Yo, look what we see? Ha ha, sure enough, tomcat is created and initialized in the getWebServer method implemented by TomcatServletWebServerFactory.
Here, we finally found the Tomcat created there!!! A little excitement. In the Tomcat object, the configuration of some default properties is specified:
public class Tomcat { private static final StringManager sm = StringManager.getManager(Tomcat.class); // Some logging implementations use weak references for loggers so there is // the possibility that logging configuration could be lost if GC runs just // after Loggers are configured but before they are used. The purpose of // this Map is to retain strong references to explicitly configured loggers // so that configuration is not lost. private final Map<String, Logger> pinnedLoggers = new HashMap<>(); protected Server server; protected int port = 8080; protected String hostname = "localhost"; protected String basedir;
OK, we found the place where Tomcat was created. So, after Tomcat is created, where is Tomcat started? Let's taste tomcatser vletWebServerFactory.getWebServer () method.
Let's continue to add breakpoints in the getWebServer () method to see which method starts tomcat:
/** * Factory method called to create the {@link TomcatWebServer}. Subclasses can * override this method to return a different {@link TomcatWebServer} or apply * additional processing to the Tomcat server. * @param tomcat the Tomcat server. * @return a new {@link TomcatWebServer} instance */ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0); }
According to the debugging results, the tomcat application is started when the gettomcat web server (tomcat Tomcat) is executed. Gettomcat webserver (tomcat Tomcat) returns an instance of tomcat webserver. So tomcat must be started in the constructor of tomcat webserver. Let's take a look.
Initialization and startup in Tomcat web server/** * Create a new {@link TomcatWebServer} instance. * @param tomcat the underlying Tomcat server * @param autoStart if the server should be started */ public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); }
Take a look at the initialize method:
TomcatWebServer.initialize methodprivate void initialize() throws WebServerException { 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())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
At this point, we have found all the new paths of how spring boot initializes and starts tomcat, and I have also learned the source code at a cost from my first perspective of source code.
Thinking and summaryIf you just want to know the process of tomcat in SpringBoot, you can finish here. Next, let's think about another problem. In actual production, we will start multiple service s to a tomcat at the same time. So how does tomcat do it? This is about how tomcat works. So, interested students continue to watch with me.
How Tomcat works
introductionIn the above, when we look at getWebServer in TomcatServletWebServerFactory, we don't know whether you remember these codes:
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } // 1. Create tomcat instance Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); customizeConnector(connector); // 2. The tomcat setting connector will be created tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); // 3. Configure engine configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); // 4. Start tomcat return getTomcatWebServer(tomcat); }
What the hell is Connector? Why put it in the Tomcat instance? In the configureEngine () method, what is the Engine obtained from the Tomcat instance?
EngineWe see that in the method getWebServer of creating Tomcat instance, configureengine is executed in step 4( tomcat.getEngine ()) method obtains Engine. Let's look at the source code of this method to understand what Engine is:
/** * Access to the engine, for further customization. * @return The engine */ public Engine getEngine() { // 1. Get the first service in the service Service service = getServer().findServices()[0]; // 2. If the obtained service is already in the Engine container, it will be returned to the current Engine container if (service.getContainer() != null) { return service.getContainer(); } // 3. If it does not exist, create a container named tomcat and return Engine engine = new StandardEngine(); engine.setName( "Tomcat" ); engine.setDefaultHost(hostname); engine.setRealm(createDefaultRealm()); service.setContainer(engine); return engine; }
According to the source code definition, it seems that Engine is a container for service classes. So, what is the essence of Engine container? We still need to look at the source code of Engine:
public interface Engine extends Container { /** * @return the default host name for this Engine. */ public String getDefaultHost(); /** * Set the default hostname for this Engine. * * @param defaultHost The new default host */ public void setDefaultHost(String defaultHost); /** * @return the JvmRouteId for this engine. */ public String getJvmRoute(); /** * Set the JvmRouteId for this engine. * * @param jvmRouteId the (new) JVM Route ID. Each Engine within a cluster * must have a unique JVM Route ID. */ public void setJvmRoute(String jvmRouteId); /** * @return the <code>Service</code> with which we are associated (if any). */ public Service getService(); /** * Set the <code>Service</code> with which we are associated (if any). * * @param service The service that owns this Engine */ public void setService(Service service); }
We can see that the main methods provided in the Engine interface are basically some methods for the whole application and runtime JVM configuration. Then, let's take a look at the description of this interface:
/** * An <b>Engine</b> is a Container that represents the entire Catalina servlet * engine. It is useful in the following types of scenarios: * <ul> * <li>You wish to use Interceptors that see every single request processed * by the entire engine. * <li>You wish to run Catalina in with a standalone HTTP connector, but still * want support for multiple virtual hosts. * </ul> * In general, you would not use an Engine when deploying Catalina connected * to a web server (such as Apache), because the Connector will have * utilized the web server's facilities to determine which Context (or * perhaps even which Wrapper) should be utilized to process this request. * <p> * The child containers attached to an Engine are generally implementations * of Host (representing a virtual host) or Context (representing individual * an individual servlet context), depending upon the Engine implementation. * <p> * If used, an Engine is always the top level Container in a Catalina * hierarchy. Therefore, the implementation's <code>setParent()</code> method * should throw <code>IllegalArgumentException</code>. * * @author Craig R. McClanahan */It can be summarized as follows:
1. Engine represents the container engine of the entire Catalina Servlet
2. When running Catalina to web applications, containers are generally not used, but they can also be used
3. If you use a container Engine, the Engine must be the top-level container
ContainerThen, since Engine is the top-level Container, its parent class Container is the parent Container. Let's take a look at the Container interface.
Container is the parent interface of the container. The container is designed with the typical design pattern of responsibility chain. It is composed of four self container components, namely Engine, Host, Context and Wrapper. These four components are responsible relationships, and there are containment relationships. Usually a Servlet class corresponds to a Wrapper. If there are multiple servlets defining multiple wrappers, a higher container, such as Context, should be defined.
Context can also be defined in the parent container host. Host is not required, but to run the war program, host is required, because there must be web.xml File. The resolution of this file requires a host. If there are multiple hosts, you need to define a top container Engine. There is no parent container for Engine. An Engine represents a complete Servlet Engine.
Tomcat also has other important components, such as security, logger log, session, mbeans, naming and other components. Together, these components provide the necessary services for the Connector and Container.
We can see from the class diagram of Container that as the superior interface of Engine, the Container has Context, Host and Wapper as the sub interface at the same level with Engine. So, what is the relationship between the four interfaces?
Relationship among Host, Context, Wapper and Engine//Part of the source code, the rest is omitted. public class Tomcat { //Set up connector public void setConnector(Connector connector) { Service service = getService(); boolean found = false; for (Connector serviceConnector : service.findConnectors()) { if (connector == serviceConnector) { found = true; } } if (!found) { service.addConnector(connector); } } //Get service public Service getService() { return getServer().findServices()[0]; } //Setting up the Host container public void setHost(Host host) { Engine engine = getEngine(); boolean found = false; for (Container engineHost : engine.findChildren()) { if (engineHost == host) { found = true; } } if (!found) { engine.addChild(host); } } //Get Engine container 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; } //Get server public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); server = new StandardServer(); initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); server.setPort( -1 ); Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; } //Add Context container public Context addContext(Host host, String contextPath, String contextName, String dir) { silence(host, contextName); Context ctx = createContext(host, contextPath); ctx.setName(contextName); ctx.setPath(contextPath); ctx.setDocBase(dir); ctx.addLifecycleListener(new FixContextListener()); if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } //Add Wrapper container public static Wrapper addServlet(Context ctx, String servletName, Servlet servlet) { // will do class for name and set init params Wrapper sw = new ExistingStandardWrapper(servlet); sw.setName(servletName); ctx.addChild(sw); return sw; } }
Through the method of Tomcat class, we can know the following relationships:
1. Through the getServer () method: a Tomcat corresponds to a Server
2. Through the getEngine () method: a Server corresponds to multiple services; a Service represents our application; there is only one Service in an Engine
3. Through the setHost () method: there can be multiple engines in a Host
4. Through the addContext () method: Context can have multiple in Tomcat
5. With the addServlet () method: the Wrapper container can have multiple
6. Through the setconnector () method: the connector is set under service. A service can have multiple connectors
Among them, the Engine processes all client requests for a specific Service component, and the Host component processes all client requests for a specific virtual host. The Context component processes all client requests for a specific Web application.
The connector class element represents the communication interface between the client and the service, which is responsible for sending the client's request to the server and passing the server's response to the client.
Operation schematic diagram1. When a user clicks on the content of the web page, the request is sent to the local port 8080 and obtained by the Coyote HTTP/1.1 Connector listening there.
2. In this queue, the request is added to the queue of tomservice, and the task is sent to the queue to process the task.
3. Engine get request localhost/test/index.jsp , matching all virtual Host hosts.
4. The Engine matches to the Host named localhost (even if it doesn't match, the Host will handle the request, because the Host is defined as the default Host of the Engine). The Host named localhost gets the request / test/index.jsp , matching all the contexts it owns. The Host matches the Context with the path of / test (if not, the request will be handed over to the Context with pathname '').
5. Context get request for path = "/ test"/ index.jsp , find the corresponding Servlet in its mapping table. Context matches the Servlet whose URL PATTERN is *. JSP, corresponding to the JspServlet class.
6. Construct HttpServletRequest object and HttpServletResponse object, call doGet() or doPost() of JspServlet as parameters, execute business logic, data storage and other programs.
7. Context returns the HttpServletResponse object to the Host after execution.
8. The Host returns the HttpServletResponse object to the Engine.
9. The Engine returns the HttpServletResponse object to the Connector.
10. Connector returns the HttpServletResponse object to the customer Browser.