Principle analysis of embedding Tomcat in SpringBoot

Spring boot embedded Tomcat principle

Embedded Tomcat startup principle

  1. First, come to the place where the SpringBoot project is started, that is, the configuration class
@SpringBootApplication
public class InakiApplication {

    public static void main(String[] args) {
        SpringApplication.run(InakiApplication.class, args);
    }

}
  1. Click the @ SpringBootApplication annotation to enter


3. In the figure above, the * * @ Import annotation is used to introduce the AutoConfigurationImportSelector * * class. What does this class do? When you enter the source code, you first call the **selectImport() * method. In this method, you call the **getAutoConfigurationEntry () method, then you call the getCandidateConfigurations() * method, and the getCandidateConfigurations() method goes to META-INF/spring.. Load relevant configuration classes in the factories configuration file.

META-INF/spring. Factories: SPI mechanism of SpringBoot. When SpringBoot starts, it will scan all files with this name, get all classes that need to be automatically assembled, and scan them.


META-INF/spring.factories in spring boot autoconfiguration: XXX In the release package

Continue to view the org.org of the modified configuration class springframework. boot. autoconfigure. Value of enableautoconfiguration
The automatic configuration class of tomcat server is the servletwebserverfactory autoconfiguration. Click to enter this class.

4. First look at the notes of this class

Auto-configuration for servlet web servers.

Automatic configuration of servlet web server

  1. Continue to look at the annotation of this class

    In this class, embedded container classes such as EmbeddedTomcat, EmbeddedJetty and embedded undertow are also loaded through the @ Import annotation. springboot starts the embedded tomcat container by default. If you want to change the startup jetty or underow container

  2. Take a look at the ServerProperties class in the EnableConfigurationProperties annotation

// Go to the configuration file to find the configuration information starting with server
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * Set server port number
	 */
	private Integer port;

	/**
	 * Set web server address
	 */
	private InetAddress address;

This proves why we need to be in application properties/application. Configure server in YML file port,server.address will take effect.

  1. We click to enter the EmbeddedTomcat class

Mainly focus on the method of tomcatServletWebServerFactory

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
	
    // The class must meet the conditions declared in the @ ConditionalOnClass and @ ConditionalOnMissingBean annotations to take effect
    // @ Con@ConditionalOnMissingBeanditionalOnClass , since we imported the dependency package of tomcat, these three classes must exist in all contexts
    // @ConditionalOnMissingBean, the ServletWebServerFactory class does not exist in the context
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory(
				ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
				ObjectProvider<TomcatContextCustomizer> contextCustomizers,
				ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
            // 1. Create Tomcat webserver factory
			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            // The following methods are used to set the property value of tomcatwebServeFactory
			factory.getTomcatConnectorCustomizers()
					.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatContextCustomizers()
					.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatProtocolHandlerCustomizers()
					.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}

	}

	/**
	 * Because the package of jetty is not imported, the conditions in the second annotation cannot be met, so this class will not be loaded and will not be automatically configured
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyServletWebServerFactory JettyServletWebServerFactory(
				ObjectProvider<JettyServerCustomizer> serverCustomizers) {
			JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
			factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}

	}

	/**
	 * Similarly, embeddedjetty class
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowServletWebServerFactory undertowServletWebServerFactory(
				ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
				ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
			UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
			factory.getDeploymentInfoCustomizers()
					.addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}

	}

}

The code in the text has been annotated.

  1. In this class, only the embbedtomcat condition can be satisfied. Now go to TomcatServletWebServerFactory

The following is a key method getWebServer. It is the class that creates Tomcat and completes the startup.

public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
    	// Create tomcat
		Tomcat tomcat = new Tomcat();
    	// Set baseDir
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
    	// Gets the Connector for receiving and processing requests
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
    	// Add Connector to webServer server
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
    	// Turn off automatic deployment
		tomcat.getHost().setAutoDeploy(false);
    	// Set Engine
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

Finally, return getTomcatWebServer(tomcat); Continue to getTomcatWebServer()

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		return new TomcatWebServer(tomcat, getPort() >= 0);
	}

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
    	// The startup method is here
		initialize();
}


Here is how SpringBoot automatically configures and starts the Tomcat container.

Where is Tomcat configured during SpringBoot startup

As mentioned above, the core method of tomcat startup is in the TomcatServletWebServerFactory#getWebServer() method,

Let's take a look at where getWebServer is called.

Call process of getWebServer

Click the run method to enter the SpringApplication#run(String... args) method.

public ConfigurableApplicationContext run(String... args) {
    //StopWatch is mainly used to count the execution time of each task, such as the total time taken by Spring Boot startup.
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new
    ArrayList<>();
    configureHeadlessProperty();
    //Step 1: get and start the listener by loading meta-inf / spring Factories completed
    SpringApplicationRunListener Instantiation work
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //In fact, it calls the starting() method of the EventPublishingRunListener class
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new
        DefaultApplicationArguments(args);
        //Step 2: construct container environment. In short, load system variables, environment variables and configuration files
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
        applicationArguments);
        //Set the bean to be ignored
        configureIgnoreBeanInfo(environment);
        //Print banner
        Banner printedBanner = printBanner(environment);
        //Step 3: create container
        context = createApplicationContext();
        //Step 4: instantiate springbootexceptionreporter Class, used to support the report on startup
        error
        exceptionReporters =
        getSpringFactoriesInstances(SpringBootExceptionReporter.class,
        new Class[] { ConfigurableApplicationContext.class },
        context);
        //Step 5: prepare container this step is mainly the preparation action before container refresh. Contains a key operation:
        Inject the startup class into the container to lay the foundation for subsequent automatic configuration.
        prepareContext(context, environment, listeners,
        applicationArguments, printedBanner);
        //Step 6: the processing work related to refreshing the container springBoot has been completed, and the next work will be handed over to you
        spring.  Internal call spring of refresh method,
        // refresh method plays an important role in the whole source code system of spring and is the key to realize ioc and aop.
        refreshContext(context);
        //Step 7: refresh the template method in the extension interface design pattern after the container. It is empty by default. If there is customization, you need to
        The method can be overridden. For example, print some startup ends logļ¼ŒOr some other post-processing.
        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);
        /*
        This method probably does the following things
        1. Get and start the listener by loading meta-inf / spring Factories completed
        SpringApplicationRunListener Instantiation work
        2. Constructing container environment, in short, is to load system variables, environment variables and configuration files
        3. Create container
        4. Instantiate springbootexceptionreporter Class, used to support reporting errors about startup
        5. Prepare container
        6. Refresh container
        7. Extension interface after refreshing container
        Then the built-in tomcat startup source code is hidden in step 6 of the appeal: the refreshContext method, which will eventually be called
        Use the refresh() method of AbstractApplicationContext class
        Enter the refreshContext() method, as shown in the figure:
        * Traverse all registered ApplicationRunner and CommandLineRunner, and execute their run() side
        Law.
        * We can implement our own ApplicationRunner or CommandLineRunner to
        SpringBoot Expand the startup process of.
        */
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        //Listening events that the application has started and completed
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

The main process of this method is the following seven methods, and the serial number has been marked in the above code.

  1. Get and start the listener by loading meta-inf / spring Factories completed the instantiation of spring applicationrunlistener
  2. Constructing container environment, in short, is to load system variables, environment variables and configuration files
  3. Create container
  4. Instantiate springbootexceptionreporter Class, used to support reporting errors about startup
  5. Access container
  6. Refresh container refresh
  7. Refresh container's extension interface

The process of building in tomcat is in the refresh() method in step 6

Click enter to go to AbstractApplicationContext#refresh() The code has been streamlined

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare context
			prepareRefresh();

			// Get beanFactory
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Set some properties and fill in beanFactory
			prepareBeanFactory(beanFactory);

			try {
				// Load the post processor of the assembly beanFactory
				postProcessBeanFactory(beanFactory);

				// Post processor to execute beanFactory
                // The acquisition of automatic configuration class is here. At this time, all beans are saved in BeanDefinition.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register beanpossessor bean level postprocessor
				registerBeanPostProcessors(beanFactory);

				// Dealing with internationalization related content
				initMessageSource();

				// Initialize applicationeventmulticast
				initApplicationEventMulticaster();

				// Load the web server, and the initialization of tomcat is here
				onRefresh();

				// Register listener
				registerListeners();

				// Load all bean s with delayed initialization
				finishBeanFactoryInitialization(beanFactory);

                // Complete the startup of the web server
				finishRefresh();
			}
		}
	}

The assembly process of tomcat is in onRefresh() method. Click to enter.

After entering, it is found that this is a template method, and subclasses are needed to rewrite the implementation.

Click to enter servletwebserver ApplicationContext

protected void onRefresh() {
    // Call parent method
		super.onRefresh();
		try {
            // Core, create Tomcat webserver
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

createWebServer() starts the web service, but Tomcat has not been started yet

Breakpoint entry:

Both webServer and servletContext are empty. Enter 179 lines of code and get the last line of servletwebserver applicationcontext#getwebserverfactory() from the factory

What it wants to get is a factory of type servletwebserverfactory. It seems to me to analyze the servletwebserverfactory class. It is an interface, that is, there is a method of getWebServer. Does this method look familiar. Yes, it is the getWebServer method in TomcatServletWebServerFactory, which is the core configuration process of tomcat.

This factory has the XXXWebServerFactory mentioned when we talked about Tomcat automatic configuration just now. You can see Tomcat and Jetty here.
Release breakpoint:

At this point, you get TomcatServletWebServerFactory and call its getWebServer method.

At this point, the configuration process of tomcat is finished.

Summary

springboot starts a built-in Tomcat by means of new Tomcat(). But there is another problem here. Tomcat is only started here, but how does our spring MVC load? In the next chapter, we will talk about how spring boot automatically assembles spring MVC

flow chart:

Keywords: Java Spring Boot Tomcat

Added by crazy8 on Tue, 25 Jan 2022 12:07:18 +0200