本文结论

  • 源码基于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. 远见而明察近观若明火|Centos7.6环境基于Prometheus和Grafana结合钉钉机器人打造全时监控(预警)Docker容器服务系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_181 我们知道,奉行长期主义的网络公司,势必应在软件开发流程管理体系上具备规范意识,即代码提交有CR(CodeReview),功能 ...

  2. Odoo 14 Action URL 生成

    from werkzeug.urls import url_encode url = '/web#%s' % url_encode({ 'action': 'hr.plan_wizard_action ...

  3. Redis 03 字符串

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 应用场景:计数 ...

  4. QPainter. QpaintDevice 绘图设备

    QPaintDevice 绘图设备 1 QPixmap QImage Qbitmap(黑白色) QPicture QWidget 2 QPixmap 对不同平台做了显示优化  fill(填充颜色) Q ...

  5. 前端Long类型丢失精度问题

    有时候后端向前端传输Long类型,数字过长会出现丢失精度的问题 比如后端传来的是这样一个长数字串 那么前端的弹窗显示的是 ![](https://img2022.cnblogs.com/blog/22 ...

  6. Typora多线程批量上传图片,永久免费25G图床

    为了满足日常需求,就写了一个自动上传图片到图床的脚本 运行该程序可以做到自动完成图片上传,并自动替换为网络链接,支持多图同时上传,采用了多线程,上传速度提升很明显. 以Window系统为例,操作步骤: ...

  7. KingbaseES R6 集群创建流复制只读副本库案例

    一.环境概述 [kingbase@node2 bin]$ ./ksql -U system test ksql (V8.0) Type "help" for help. test= ...

  8. InnoDB_锁总结

    1. 查询会对资源添加共享锁 加了共享锁的资源不可以被修改:但可以被查询(也是会在资源上再加共享锁) 2. 数据修改会对资源添加排他锁 加了排他锁的资源只能被持有这个排他锁的事务读取和修改,其他事务读 ...

  9. 【读书笔记】C#高级编程 第十二章 动态语言扩展

    (一)DLR C#4的动态功能是Dynamic Language Runtime(动态语言运行时,DLR)的一部分.DLR是添加到CLR的一系列服务. (二)dynamic类型 dynamic类型允许 ...

  10. 新一代网络请求库:python-httpx库

    目录 httpx库 一. 概述 1. 简介 2. 命令行模式 3. 快速开始 3.1 get请求 3.2 post请求 3.2.1 表单 3.2.2 文件 3.2.3 JSON 3.2.4 二进制 3 ...