SpringBoot中内嵌Tomcat的实现原理解析

596 阅读6分钟

博客地址:blog.ljbmxsm.com/springboot/…

1. 什么是自动装配

在SpringBoot源码中有一个spring-boot-autoconfigure模块,在这个模块中做了很多默认的配置。也就是我们俗称的“自动装配”。
在SpringBoot的自动装配会根据添加的依赖,自动加载依赖相关的配置属性并启动依赖,自动装配的底层原理: spring的条件注解@Conditional来实现

2. 为什么要自动装配

利用自动装配模式代替XML配置模式,比如使用SpringMVC时,需要配置组件扫描、调度器、试图解析器等,现在有了自动装配,这些都可以不用配置了,SpringBoot默认已经帮我们配置好了。利用内嵌的Tomcat通过自动装配就不需要配置外置的Tomcat,减少配置的麻烦。说白了自动装配就是减少了配置。

3. SpringBoot Tomcat自动装配详解

我们会从下面四个方面去分析Tomcat的自动装配。

  • SpringBoot项目配置
  • SpringBoot项目启动
  • SpringBoot Tomcat如何装配
  • SpringBoot Tomcat如何启动

3.1 SpringBoot项目配置

SpringBoot官网的pom.xml文件配置:



<?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>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-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>

其实上面的主要有用的就两个配置:

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

这个配置就是设置SpringBoot的版本,还有就是另外一个配置:

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

这个就是最重要的。这个依赖实现了自动装配,我们接着往下看这个依赖的源码。

在SpringBoot项目的有一个 spring-boot-starters 模块,通过源码可以看到这个模块里面都是pom.xml的pom文件,那么我们来看一下 spring-boot-starter-web 模块的pom文件。

<?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>
	<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>

通过上面可以看出来导入了 spring-webspring-webmvc 。同时还导入了 spring-boot-starter-tomcatspring-boot-starter 依赖。

从这里可以看出来SpringBoot默认的启动容器是tomcat

看一下 spring-boot-starter 的pom文件

<?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>
	<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>

通过上面可以看出来导入一个重要的模块 spring-boot-autoconfigure 这个模块里面包含了所有的代码。

通过 spring-boot-autoconfigure 模块可以看出来,所有的SpringBoot starter的代码都在这个模块,对于不是web项目的SpringBoot项目,只需要引入:

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

看一下 spring-boot-starter-tomcat 的pom文件:

<?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>
	<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>

而这个里面的导入的是内嵌的Tomcat。这里就分析完成了在SpringBoot的配置pom文件配置导入了一些什么东西。

SpringBoot项目启动

看一下我们一般SpringBoot项目启动的代码写法:

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

通过 main 方法来启动,之前有一篇 《SpringBoot启动分析》的文章。讲解了SpringBoot是如何启动的,在另外一篇文章中 《SpringBoot源码解析之autoconfigure》讲解了自动装配的底层原理。而在之前的项目配置引入依赖引入了 spring-boot-autoconfigure 这个模块。通过查看该模块的 spring.factories 的数据有一个自动配置的有这样一个配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

这个类揭示了Tomcat如何自动装配。

SpringBoot Tomcat如何装配

研究一下 ServletWebServerFactoryAutoConfiguration 类的源码。探索Tomcat如何自动装配

注意:自动装配的底层原理根据的是Spring框架的@Conditional注解

@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 {
	//省略代码

}

@ConditionalOnClass(ServletRequest.class) 注解判断classpath中是否有 ServletRequest 类。

@ConditionalOnWebApplication(type = Type.SERVLET) 判断是否为servlet。

@EnableConfigurationProperties(ServerProperties.class) 加载 ServerProperties 配置。

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

这一段导入了 BeanPostProcessorsRegistrar 和 三个web运行容器:

ServletWebServerFactoryAutoConfiguration 有三个方法一个静态内部类。

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

servletWebServerFactoryCustomizer 方法创建一个ServletWebServerFactoryCustomizer(定制器)。

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

tomcatServletWebServerFactoryCustomizer 方法根据 ConditionalOnClass 存在 Tomcat那么创建 TomcatServletWebServerFactoryCustomizer 定制器。

@Bean
	@ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
	@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
	public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
		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;
	}

forwardedHeaderFilter 创建的处理转发头的过滤器。
ServletWebServerFactoryAutoConfiguration 类中还有一个静态内部类:

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);
			}
		}

	}

在这个静态内部类中往SpringApplication的Context中注入两个Bean的处理器:

  • WebServerFactoryCustomizerBeanPostProcessor
    WebServerFactoryCustomizer bean的处理
  • ErrorPageRegistrarBeanPostProcessor
    ErrorPageRegistrar bean处理器

ServletWebServerFactoryAutoConfiguration 类上面有一个@Import注解导入了四个类:

ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
ServletWebServerFactoryConfiguration.EmbeddedJetty.class
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class

第一个就是ServletWebServerFactoryAutoConfiguration中的静态类,然后我们研究一下 EmbeddedTomcat 中的静态内部类:

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

    @Configuration(proxyBeanMethods = false)
    //存在有Tomcat的类--这个是Apache Tomcat的启动类(内嵌Tomcat)
	@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) {
			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()));
			return factory;
		}

	}
    
    //省略其他的代码两个静态内部类
}

在这个静态内部类中做了一件事情,那就是创建 TomcatServletWebServerFactory 类。在这个类中有一个 getWebServer 方法,这个方法在接口 ServletWebServerFactory#getWebServer 中。那么看一下这个类的实现:

	@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);
	}

可以看出来这个方法主要是创建内嵌的Tomcat。

TomcatServletWebServerFactory#getWebServer中就创建了内嵌的Tomcat。以前的Tomcat都是配置在外面,SpringBoot主要是通过内嵌的Web容器取消了开发过程中配置。

SpringBoot Tomcat如何启动

分析了如何创建内嵌的Tomcat,那么对于SpringBoot是如何启动Tomcat的。对于Tomcat的启动分为两个方面分析:

  1. TomcatServletWebServerFactory#getWebServer什么时候调用?
  2. Tomcat什么时候启动
Tomcat容器创建
st=>start: 开始
spRun=>operation: SpringApplication#run
spCreateApplicationContext=>operation: SpringApplication#createApplicationContext
spRefreshContextcondition=>operation: SpringApplication#refreshContext
e=>end: 结束

st->spRun->spCreateApplicationContext->spRefreshContextcondition->e

上面是SpringApplication的主要几个方法。在方法refreshContext中主要是刷新上下文:

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

通过代码发现主要是通过调用 AbstractApplicationContext#refresh 方法。这个方法很熟悉,SpringApplication中创建的ApplicationContext主要创建的是 AnnotationConfigServletWebServerApplicationContext 。而这个类继承了 ServletWebServerApplicationContext 类,当前类重载了 onRefresh 方法。那么看一下 ServletWebServerApplicationContext#onRefresh 方法代码:

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
		   //创建WebServer(对于Tomcat的容器创建的是TomcatWebServer)
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

通过调用 onRefresh 方法创建了TomcatWebServer。 在 AbstractApplicationContext#refresh 这个方法中最后调用了一个 finishRefresh 方法。在 AbstractApplicationContext#refresh 方法中的代码:

	protected void finishRefresh:
	() {
		// 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);
	}

在ServletWebServerApplicationContext重写了方法finishRefresh:

@Override
	protected void finishRefresh() {
	    //调用了父类的完成刷新方法
		super.finishRefresh();
		//启动webServer
		WebServer webServer = startWebServer();
		if (webServer != null) {
		    //发布ServletWebServerInitializedEvent事件
			publishEvent(new ServletWebServerInitializedEvent(webServer, this));
		}
	}

所以这里就启动了Web容器。

通过这里发现创建WebServer是在onRefresh方法中创建了,而启动WebServer是在 finishRefresh中启动。启动过程中还推送了ServletWebServerInitializedEvent事件。

通过源码发现TomcatServletWebServerFactory#getWebServer是在ServletWebServerApplicationContext#onRefresh方法中调用了createWebServer方法来触发的。

Tomcat什么时候启动

说到这个Tomcat启动事件,很多人都比较困惑。通过代码分析可以发现在如果把SpringBoot当做一个简单的Spring的应用,从初始化到完成Spring环境的完成。Tomcat是在SpringApplication完成后启动的,然后发布ServletWebServerInitializedEvent事件。