本文结论

  • 源码基于spring boot2.6.6
  • 项目的pom.xml中存在spring-boot-starter-web的时候,在项目启动时候就会自动启动一个Tomcat。
  • 自动配置类ServletWebServerFactoryAutoConfiguration找到系统中的所有web容器。我们以tomcat为主。
  • 构建TomcatServletWebServerFactory的bean。
  • SpringBoot的启动过程中,会调用核心的refresh方法,内部会执行onRefresh()方法,onRefresh()方法是一个模板方法,他会执行会执行子类ServletWebServerApplicationContext的onRefresh()方法。
  • onRefresh()方法中调用getWebServer启动web容器。

spring-boot-starter-web内部有什么?

  • 在spring-boot-starter-web这个starter中,其实内部间接的引入了spring-boot-starter-tomcat这个starter,这个spring-boot-starter-tomcat又引入了tomcat-embed-core依赖,所以只要我们项目中依赖了spring-boot-starter-web就相当于依赖了Tomcat。

自动配置类:ServletWebServerFactoryAutoConfiguration

  • 在spring-boot-autoconfigure-2.6.6.jar这个包中的spring.factories文件内,配置了大量的自动配置类,其中就包括自动配置tomcat的自动配置类:ServletWebServerFactoryAutoConfiguration

自动配置类的代码如下

// full模式
@Configuration(proxyBeanMethods = false) // 配置类解析顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 条件注解:表示项目依赖中要有ServletRequest类(server api)
@ConditionalOnClass(ServletRequest.class)
// 表示项目应用类型得是SpringMVC(在启动过程中获取的SpringBoot应用类型)
@ConditionalOnWebApplication(type = Type.SERVLET) // 读取server下的配置文件
@EnableConfigurationProperties(ServerProperties.class) // import具体的加载配置的类和具体web实现容器
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
......
}
  • ServletRequest是存在于tomcat-embed-core-9.0.60.jar中的的一个类,所以@ConditionalOnClass(ServletRequest.clas s)会满足。
  • spring-boot-starter-web中,间接的引入了spring-web、spring-webmvc等依赖,所以@ConditionalOnWebApplication(type = Type.SERVLET)条件满足。
  • 上面的俩个条件都满足,所以spring回去解析这个配置类,在解析过程中会发现他import了三个类!我们重点关注EmbeddedTomcat。其他俩个的内部条件注解不满足!
@Configuration(proxyBeanMethods = false)
// tomcat内部的类,肯定都存在
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) // 程序员如果自定义了ServletWebServerFactory的Bean,那么这个Bean就不加载。
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
// orderedStream()调用时会去Spring容器中找到TomcatConnectorCustomizer类型的Bean,默认是没有的,程序员可以自己定义。这个Bean可以设置一些tomcat的配置,比如端口、协议...
// TomcatConnectorCustomizer:是用来配置Tomcat中的Connector组件的
factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
// TomcatContextCustomizer:是用来配置Tomcat中的Context组件的
factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
// TomcatProtocolHandlerCustomizer:是用来配置Tomcat中的ProtocolHandler组件的
factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
}
  • 对于另外的EmbeddedJetty和EmbeddedUndertow,逻辑类似,都是判断项目依赖中是否有Jetty和Undertow的依赖,如果有,那么对应在Spring容器中就会存在JettyServletWebServerFactory类型的Bean、或者存在UndertowServletWebServerFactory类型的Bean。

TomcatServletWebServerFactory的作用:获取WebServer对象

  • TomcatServletWebServerFactory他实现了ServletWebServerFactory这个接口。
  • ServletWebServerFactory接口内部只有一个方法是获取WebServer对象。

  • WebServer拥有启动、停止、获取端口等方法,就会发现WebServer其实指的就是Tomcat、Jetty、Undertow。

  • 而TomcatServletWebServerFactory就是用来生成Tomcat所对应的WebServer对象,具体一点就是TomcatWebServer对象,并且在生成TomcatWebServer对象时会把Tomcat给启动起来。
  • 在源码中,调用TomcatServletWebServerFactory对象的getWebServer()方法时就会启动Tomcat。
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 构建tomcat对象
Tomcat tomcat = new Tomcat(); // 设置相关的属性
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
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); // 启动tomcat,这个方法内部有this.tomcat.start();
return getTomcatWebServer(tomcat);
}

spring boot启动的时候启动tomcat

  • SpringBoot的启动过程中,会调用核心的refresh方法,内部会执行onRefresh()方法,onRefresh()方法是一个模板方法,他会执行会执行子类ServletWebServerApplicationContext的onRefresh()方法。
protected void onRefresh() {
// 模板方法,先调用它父类的,一般是空方法
super.onRefresh();
try {
// 创建web容器
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
  • 这个方法会调用createWebServer()方法。
// 最核心的俩行代码
private void createWebServer() {
......
// 获取web容器,多个或者没有的时候报错
ServletWebServerFactory factory = getWebServerFactory(); // 调用这个容器的getWebServer方法,上面的启动tomcat的方法!
this.webServer = factory.getWebServer(getSelfInitializer());
......
}
  • getWebServerFactory控制项目组有且只能有一个web容器!
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy // 得到所有类型为ServletWebServerFactory的Bean。TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory都是他得到子类!
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));
} // 返回唯一的一个web容器!
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

获取tomcat的配置

  • 自动配置类ServletWebServerFactoryAutoConfiguration上除了import三个web容器,还import了BeanPostProcessorsRegistrar。
  • BeanPostProcessorsRegistrar实现了ImportBeanDefinitionRegistrar,所以他会在spring启动的时候调用registerBeanDefinitions方法。
  • registerBeanDefinitions会注册一个Bean:webServerFactoryCustomizerBeanPostProcessor。
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// Bean工厂,一个Aware回调进行赋值
if (this.beanFactory == null) {
return;
}
// 注册webServerFactoryCustomizerBeanPostProcessor这个Bean。
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class,
WebServerFactoryCustomizerBeanPostProcessor::new); // 注册errorPageRegistrarBeanPostProcessor
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
}
  • webServerFactoryCustomizerBeanPostProcessor实现了BeanPostProcessor,所以他会在启动的时候调用postProcessBeforeInitialization方法。
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
// 找到WebServerFactoryCustomizer的Bean
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
// 标记日志用的类
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
// 调用customize方法,传入webServerFactory
.invoke((customizer) -> customizer.customize(webServerFactory));
}
  • postProcessBeforeInitialization中会调用WebServerFactoryCustomizer类customize方法,在系统中的唯一实现:ServletWebServerFactoryCustomizer的customize方法。
  • customize把配置中的内容设置到ConfigurableServletWebServerFactory对象中。他的实现TomcatServletWebServerFactory在启动的时候就会有值!
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.serverProperties::getPort).to(factory::setPort);
map.from(this.serverProperties::getAddress).to(factory::setAddress);
map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
map.from(this.serverProperties::getSsl).to(factory::setSsl);
map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
map.from(this.serverProperties::getCompression).to(factory::setCompression);
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
registrar.register(factory);
}
if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
}
}

ServletWebServerFactoryCustomizer这个Bean是哪里的?

  • 在我们自动配置类ServletWebServerFactoryAutoConfiguration中定义。
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars, ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
return new ServletWebServerFactoryCustomizer(serverProperties,webListenerRegistrars.orderedStream().collect(Collectors.toList()),cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
}

结束语

  • 你的点赞是我提高文章质量最大的动力!!!
  • 获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!
  • 目前已经完成了并发编程、MySQL、spring源码、Mybatis的源码。可以在公众号下方菜单点击查看之前的文章!
  • 这个公众号的所有技术点,会分析的很深入!
  • 这个公众号,无广告!!!

【Spring boot】整合tomcat底层原理的更多相关文章

  1. spring boot 整合 mybatis 以及原理

    同上一篇文章一样,spring boot 整合 mybatis过程中没有看见SqlSessionFactory,sqlsession(sqlsessionTemplate),就连在spring框架整合 ...

  2. spring boot整合jsp的那些坑(spring boot 学习笔记之三)

    Spring Boot 整合 Jsp 步骤: 1.新建一个spring boot项目 2.修改pom文件 <dependency>            <groupId>or ...

  3. spring boot 系列之四:spring boot 整合JPA

    上一篇我们讲了spring boot 整合JdbcTemplate来进行数据的持久化, 这篇我们来说下怎么通过spring boot 整合JPA来实现数据的持久化. 一.代码实现 修改pom,引入依赖 ...

  4. spring boot整合Hadoop

    最近需要用spring boot + mybatis整合hadoop,其中也有碰到一些坑,记录下来方便后面的人少走些弯路. 背景呢是因为需要在 web 中上传文件到 hdfs ,所以需要在spring ...

  5. spring boot 整合 百度ueditor富文本

    百度的富文本没有提供Java版本的,只给提供了jsp版本,但是呢spring boot 如果是使用内置tomcat启动的话整合jsp是非常困难得,今天小编给大家带来spring boot整合百度富文本 ...

  6. spring boot整合servlet、filter、Listener等组件方式

    创建一个maven项目,然后此项目继承一个父项目:org.springframework.boot 1.创建一个maven项目: 2.点击next后配置父项目及版本号 3.点击finish后就可查看p ...

  7. Spring Boot初识(2)- Spring Boot整合Mybaties

    一.本文介绍 首先读这篇文章之前如果没有接触过Spring Boot可以看一下之前的文章,并且读这篇文章还需要你至少能写基本的sql语句.我在写这篇文章之前也想过到底是选择JPA还是Mybaties作 ...

  8. Spring boot整合jsp

    这几天在集中学习Spring boot+Shiro框架,因为之前view层用jsp比较多,所以想在spring boot中配置jsp,但是spring boot官方不推荐使用jsp,因为jsp相对于一 ...

  9. spring boot 2.x 系列 —— spring boot 整合 servlet 3.0

    文章目录 一.说明 1.1 项目结构说明 1.2 项目依赖 二.采用spring 注册方式整合 servlet 2.1 新建过滤器.监听器和servlet 2.2 注册过滤器.监听器和servlet ...

随机推荐

  1. WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!

    继 Tabby.Warp 后,今天再来给大家推荐一款终端神器--WindTerm,完全开源,在 GitHub 上已经收获 6.6k 的 star. https://github.com/kingToo ...

  2. cmd命令行工具

    在windows下进行python开发,需要经常使用cmd命令行工具.打开命令行工具有很多种方法,最简单的就是win键+R键弹出运行窗口,然后输入cmd, 就会打开下面这样的窗口. 不同版本,可能配色 ...

  3. Spring源码 06 IOC refresh方法1

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  4. java-集合排序,队列,散列表map以及如何遍历

    1.1集合排序 可以通过集合的工具类java.util.Collections的静态方法sort需要注意的时,只能对List排序,因为它有序. Collections.sort(list); 排序字符 ...

  5. EMAS Serverless到底有多便利?

    EMAS Serverless 简介 EMAS Serverless 是阿里云提供的基于Serverless技术的一站式后端开发平台,为开发者提供高可用.弹性伸缩的云开发服务,包含云函数.云数据库.云 ...

  6. vue项目打包后使用reverse-sourcemap反编译到源码(详解版)

    首先得说一下,vue项目中productionSourceMap这个属性.该属性对应的值为true|false.   当productionSourceMap: true,时: 1.打包后能看到xxx ...

  7. Minio分布式集群部署——Swarm

    最近研究minio分布式集群部署,发现网上大部分都是单服务器部署,而minio官方在github上现在也只提供了k8s和docker-compose的方式,网上有关与swarm启动minio集群的文章 ...

  8. android 逆向 smali手写helloworld

    编写Hello.smali文件 .class public LHelloWorld; .super Ljava/lang/Object; .method public static main([Lja ...

  9. 解决element-ui中组件【el-upload】一次性上传多张图片的问题

    element-ui 中的组件 el-upload默认的行为是一张图片请求一次,在项目需求中,通常是多张图片要求只向后台请求一次,下面的做法就是为了实现这样的需求 前端 <el-upload r ...

  10. 003-simonyanVeryDeepConvolutional2015(VGG)

    Very Deep Convolutional Networks for Large-Scale Image Recognition #paper 1. paper-info 1.1 Metadata ...