Blog: blog.ljbmxsm.com/springboot/…

What is auto-assembly

There is a spring-boot-Autoconfigure module in the SpringBoot source that does much of the default configuration. It is also commonly known as “automatic assembly”. Automatic assembly in SpringBoot automatically loads dependent configuration properties and starts dependencies according to added dependencies. The underlying principle of automatic assembly is Conditional annotation @Conditional of Spring.

Why auto-assemble

Use autowiring instead of XML configuration. For example, with SpringMVC, you need to configure component scanning, scheduler, attempt resolver, etc. Now with autowiring, you don’t need to configure these. SpringBoot has already configured them for you by default. Using built-in Tomcat eliminates the need to configure external Tomcat through auto assembly, reducing configuration headaches. To put it bluntly, autoloassembly is a reduction in configuration.

SpringBoot Tomcat automatic assembly details

We will analyze the automatic assembly of Tomcat from the following four aspects.

  • SpringBoot project configuration
  • The SpringBoot project starts
  • How to install SpringBoot Tomcat
  • SpringBoot How do I start Tomcat

SpringBoot project configuration

In the pom. XML file on the SpringBoot website:




      
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>

    <! -- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.3. RELEASE</version>
    </parent>

    <! -- Override inherited settings -->
    <description/>
    <developers>
        <developer/>
    </developers>
    <licenses>
        <license/>
    </licenses>
    <scm>
        <url/>
    </scm>
    <url/>

    <! -- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <! -- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

There are two main configurations that are useful:

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.3. RELEASE</version>
    </parent>
Copy the code

This configuration is to set the SpringBoot version, and there is another configuration:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
Copy the code

That’s the most important thing. This dependency implements autowiring, so let’s look at the source code for this dependency.

In SpringBoot, there is the spring-boot-Starters module, which includes poM. XML poM files, so let’s look at the spring-boot-starter-web module POM files.


      
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starters</artifactId>
		<version>${revision}</version>
	</parent>
	<artifactId>spring-boot-starter-web</artifactId>
	<name>Spring Boot Web Starter</name>
	<description>Starter for building web, including RESTful, applications using Spring
		MVC. Uses Tomcat as the default embedded container</description>
	<properties>
		<main.basedir>${basedir}/.. /.. /..</main.basedir>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-json</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.apache.tomcat.embed</groupId>
					<artifactId>tomcat-embed-el</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
		</dependency>
	</dependencies>
</project>

Copy the code

Spring-web and Spring-webMVC are imported. Spring-boot-starter-tomcat and spring-boot-starter dependencies are also imported.

The default startup container for SpringBoot is Tomcat

Take a look at the POM file for spring-boot-starter

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion > 4.0.0 < / modelVersion > < the parent > < groupId > org. Springframework. Boot < / groupId > <artifactId>spring-boot-starters</artifactId> <version>${revision}</version> </parent> <artifactId>spring-boot-starter</artifactId> <name>Spring Boot Starter</name> <description>Core starter, including auto-configuration support, logging and YAML</description> <properties> <main.basedir>${basedir}/.. /.. /.. </main.basedir> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>Copy the code

As you can see from the above import an important module spring-boot-Autoconfigure which contains all the code.

The spring-boot-autoconfigure module contains all the code for SpringBoot starter. For SpringBoot projects that are not web projects, it is only necessary to introduce:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
Copy the code

Take a look at the poM file for spring-boot-starter-Tomcat:


      
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starters</artifactId>
		<version>${revision}</version>
	</parent>
	<artifactId>spring-boot-starter-tomcat</artifactId>
	<name>Spring Boot Tomcat Starter</name>
	<description>Starter for using Tomcat as the embedded servlet container. Default
		servlet container starter used by spring-boot-starter-web</description>
	<properties>
		<main.basedir>${basedir}/.. /.. /..</main.basedir>
	</properties>
	<dependencies>
		<dependency>
			<groupId>jakarta.annotation</groupId>
			<artifactId>jakarta.annotation-api</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.apache.tomcat</groupId>
					<artifactId>tomcat-annotations-api</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-el</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-websocket</artifactId>
		</dependency>
	</dependencies>
</project>
Copy the code

In this case, the import is embedded Tomcat. This completes the analysis of what is imported into the SpringBoot configuration POM file.

The SpringBoot project starts

Take a look at our general SpringBoot project launch code:

@SpringBootApplication
public class RaftApplication{
    public static void main(String[] args) { SpringApplication.run(RaftApplication.class, args); }}Copy the code

Start using the main method, as described in the previous SpringBoot Boot Analysis article. It explains how SpringBoot starts. In another article, AutoConfigure explains the underlying principles of auto-assembly. The previous project configuration import dependency introduced the spring-boot-Autoconfigure module. By looking at the spring.factories data from the module there is an auto-configured configuration that looks like this:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
Copy the code

This class shows how Tomcat is automatically assembled.

How to install SpringBoot Tomcat

Study the ServletWebServerFactoryAutoConfiguration class’s source code. Explore how Tomcat automates

Note: The underlying principle of auto assembly is based on the @Conditional annotation of the Spring framework

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
	// omit the code

}
Copy the code

ConditionalOnClass(Servletrequest.class) comment to determine whether the ServletRequest class is present in your classpath.

@ ConditionalOnWebApplication (type = the SERVLET) determine whether for the SERVLET.

@ EnableConfigurationProperties (ServerProperties. Class) loaded ServerProperties configuration.

@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
Copy the code

This paragraph into BeanPostProcessorsRegistrar and three web container:

  • Tomcat

    Tomcat’s official website

  • Jetty

    Jetty’s official website

  • Undertow

    Undertow website

There are three methods in ServletWebServerFactoryAutoConfiguration a static inner class.

@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
	return new ServletWebServerFactoryCustomizer(serverProperties);
}
Copy the code

ServletWebServerFactoryCustomizer method to create a servletWebServerFactoryCustomizer (custom).

@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( ServerProperties serverProperties) {
	return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
Copy the code

According to the exist ConditionalOnClass Tomcat then create tomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer method Custom.

@Bean
	@ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
	@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
	public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter(a) {
		ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
		FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
		registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
		registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
		return registration;
	}
Copy the code

ForwardedHeaderFilter Specifies the filter created to handle forward headers. In ServletWebServerFactoryAutoConfiguration class has a static inner class:

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar.BeanFactoryAware {

		private ConfigurableListableBeanFactory beanFactory;

		@Override
		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
			if (beanFactory instanceof ConfigurableListableBeanFactory) {
				this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; }}@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
					WebServerFactoryCustomizerBeanPostProcessor.class);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class);
		}

		private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class
        beanClass) {
			if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true.false))) {
				RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
				beanDefinition.setSynthetic(true); registry.registerBeanDefinition(name, beanDefinition); }}}Copy the code

A handler that injects two beans into the SpringApplication Context in this static inner class:

  • WebServerFactoryCustomizerBeanPostProcessor WebServerFactoryCustomizer bean processing
  • ErrorPageRegistrarBeanPostProcessor ErrorPageRegistrar bean processors

In ServletWebServerFactoryAutoConfiguration class with a @ Import annotations into four categories:

ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
ServletWebServerFactoryConfiguration.EmbeddedJetty.class
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
Copy the code

The first is a static class of ServletWebServerFactoryAutoConfiguration, and then we study the EmbeddedTomcat the static inner class:

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

    @Configuration(proxyBeanMethods = false)
    // There are classes with Tomcat -- this is the Apache Tomcat startup class (embedded Tomcat).
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory( ObjectProvider
       
         connectorCustomizers, ObjectProvider
        
          contextCustomizers, ObjectProvider
         
          > protocolHandlerCustomizers)
         >
        
        {
			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
			factory.getTomcatConnectorCustomizers()
					.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatContextCustomizers()
					.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatProtocolHandlerCustomizers()
					.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
			returnfactory; }}// Omit the rest of the code two static inner classes
}
Copy the code

In the static inner class to do one thing, that is to create TomcatServletWebServerFactory class. In this class there is a getWebServer method, which is in the interface ServletWebServerFactory#getWebServer. So look at the implementation of this class:

@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } 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); 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); }Copy the code

As you can see, this method mainly creates embedded Tomcat.

TomcatServletWebServerFactory# getWebServer creates embedded in Tomcat. Whereas Tomcat was previously configured externally, SpringBoot was largely removed from the development process via the embedded Web container.

SpringBoot How do I start Tomcat

We analyzed how to create embedded Tomcat, then how to start Tomcat for SpringBoot. The startup of Tomcat is analyzed in two aspects:

  1. When TomcatServletWebServerFactory# getWebServer call?
  2. When does Tomcat start
Tomcat container Creation
St = > start: start spRun = > operation: SpringApplication# run spCreateApplicationContext = > operation: SpringApplication#createApplicationContext spRefreshContextcondition=>operation: SpringApplication# refreshContext e = > end: end of st - > spRun - > spCreateApplicationContext - > spRefreshContextcondition - > eCopy the code

Above are the main methods of SpringApplication. RefreshContext in the method refreshContext is mainly the refreshContext:

	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}
Copy the code

Through code found mainly by calling AbstractApplicationContext# refresh method. Are familiar with this method, SpringApplication created in the main is created AnnotationConfigServletWebServerApplicationContext ApplicationContext. And this class inherits the ServletWebServerApplicationContext class, the current class reloading onRefresh methods. So take a look at the ServletWebServerApplicationContext# onRefresh method code:

	@Override
	protected void onRefresh(a) {
		super.onRefresh();
		try {
		   // Create a WebServer(for Tomcat containers, create a TomcatWebServer)
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex); }}Copy the code

TomcatWebServer is created by calling the onRefresh method. In AbstractApplicationContext# refresh this method call a finishRefresh method at last. In AbstractApplicationContext# refresh method the code:

	protected voidFinishRefresh: () {// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event.
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}
Copy the code

In finishRefresh ServletWebServerApplicationContext rewrite the method:

@Override
	protected void finishRefresh(a) {
	    // The complete refresh method of the parent class is called
		super.finishRefresh();
		/ / start the webServer
		WebServer webServer = startWebServer();
		if(webServer ! =null) {
		    / / release ServletWebServerInitializedEvent event
			publishEvent(new ServletWebServerInitializedEvent(webServer, this)); }}Copy the code

So that’s where the Web container starts.

You can see from this that the creation of the WebServer is created in the onRefresh method, and the startup of the WebServer is started in finishRefresh. Also in the process of start push the ServletWebServerInitializedEvent event.

Is found in a source TomcatServletWebServerFactory# getWebServer in ServletWebServerApplicationContext# onRefresh method called the createWebServer to trigger .

When does Tomcat start

When it comes to the Tomcat startup event, many people are confused. Through code analysis can be found in if SpringBoot as a simple Spring application, from initialization to completion of the Spring environment. After completion of the SpringApplication Tomcat is started, and then release ServletWebServerInitializedEvent events.