当前位置: 首页 > news >正文

001-Spring boot 启动内置Web容器分析

目录

    • 代码入口
      • 上下文容器
    • 加载web容器
      • WebServer
      • createWebServer
      • getWebServerFactory():
      • getWebServer():
    • 执行WebServer#start
    • 自动配置
      • 读取配置
      • 修改配置

代码入口

上下文容器

SpringApplication.run(App.class);
//追踪下去发现
context = createApplicationContext();createApplicationContext()return this.applicationContextFactory.create(this.webApplicationType);这里用了策略模式:
读取 interface org.springframework.boot.ApplicationContextFactory 的实现
根据 webApplicationType 匹配对应的处理
case : WebApplicationType.REACTIVE : return new AnnotationConfigReactiveWebServerApplicationContext();
case : WebApplicationType.SERVLET : return new AnnotationConfigServletWebServerApplicationContext();然后就创建了:
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

webApplicationType 获取

//this.webApplicationType是在new SpringApplication()的时候赋值的
this.webApplicationType = WebApplicationType.deduceFromClasspath();WebApplicationType.deduceFromClasspath():if (classpath中 有 org.springframework.web.reactive.DispatcherHandler 
且 没有 org.springframework.web.servlet.DispatcherServlet 
且 没有 org.glassfish.jersey.servlet.ServletContainer) {return WebApplicationType.REACTIVE;
}//有 spring-web的依赖则有
if (classpath中 没有 javax.servlet.Servlet) {return WebApplicationType.NONE;
}
if (classpath中 没有 org.springframework.web.context.ConfigurableWebApplicationContext) {return WebApplicationType.NONE;
}
return WebApplicationType.SERVLET;

加载web容器

首先我们知道了现在的上下文是AnnotationConfigServletWebServerApplicationContext
看一下依赖
在这里插入图片描述

执行 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefreshorg.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
来创建一个WebServer

WebServer

我们先来分析下这个类
这个类是一个接口

void start() throws WebServerException;
void stop() throws WebServerException;
//返回监听端口
int getPort();
//优雅关闭
void shutDownGracefully(GracefulShutdownCallback callback);

createWebServer

//看下容器中是否有webServer 这里是没有的
WebServer webServer = this.webServer;
ServletContext servletContext = this.servletContext;
if (webServer == null && servletContext == null) {//获取WebServerFactory 这里会确定 web容器是 tomcat 还是其他ServletWebServerFactory factory = getWebServerFactory();//这一步就初始化容器了 设置端口啥的//这里要注意了 getWebServer入参是ServletContextInitializer 这是一个函数类//也就是说只是设置了行为而不需要执行 先执行了getWebServerthis.webServer = factory.getWebServer(getSelfInitializer());//注册优雅关闭getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));//注册启动 关闭getBeanFactory().registerSingleton("webServerStartStop",new WebServerStartStopLifecycle(this, this.webServer));
}

getWebServerFactory():

//获取Bean工厂里ServletWebServerFactory的BeanName
//这里获取的是 tomcatServletWebServerFactory
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
//这里会校验 容器中有且只能有一个ServletWebServerFactory类
if (beanNames.length == 0) {throw new MissingWebServerFactoryBeanException()}
if (beanNames.length > 1) {throw new ApplicationContextException()}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);

为什么获取的是 tomcatServletWebServerFactory 呢?

一般找不到在哪里注入的Bean的时候就可以试一下搜索XXXAutoConfiguration
就是利用自动配置类 SPI机制加载 ServletWebServerFactoryAutoConfiguration
其中引入了 
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
ServletWebServerFactoryConfiguration.EmbeddedJetty.class
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
但是这三个类都有各自创建的条件比如tomcat@Configuration(proxyBeanMethods = false)
// 这三个类在 类加载器里都存在
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
//要是有用户自定义的 ServletWebServerFactory 类型的Bean就不创建
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {//这里的ObjectProvider 意思就是有了就设置进去,没了设置一个内容为空的对象//给对Tomcat进行自定义设置的一个扩展,再配置无法满足需求的时候@BeanTomcatServletWebServerFactory 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;}
}

注意
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
这个注解里依赖的 class即使不存在也没关系 因为编译运行都不会报错
因为本来我们应用里依赖的就是 jar包
jar包就是编译好的class文件,所以不会再重复编译了
运行的时候也不是直接反射,而是利用ASM技术直接读的字节码获取引用类的全类名
然后调用类加载器加载,加载不到就是没有

getWebServer():

Tomcat tomcat = new Tomcat();
然后进行各种设置 下面三个日志就是这一步打印的
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
o.apache.catalina.core.StandardService   : Starting service [Tomcat]
org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.71]总之就是创建了服务 但是没有真正的启动因为关系是这样的 上下文的refresh()方法中
// Initialize other special beans in specific context subclasses.
// 此时还在这里
onRefresh();// Check for listener beans and register them.
registerListeners();// Instantiate all remaining (non-lazy-init) singletons.
// 创建 非懒加载的单例bean
finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.
// 最后一步 到这里 所有Bean都已经ok了 可以提供服务出来了
finishRefresh();

执行WebServer#start

finishRefresh()getLifecycleProcessor().onRefresh() 中的 onRefresh()
onRefresh()startBeans(true);startBeans(true) :
获取beanFactory 中所有的 Lifecycle.class 类型的Bean 设置到 Map<String, Lifecycle> lifecycleBeansMap<Integer, LifecycleGroup> phases = new TreeMap<>();
遍历 lifecycleBeans : (beanName, bean){if (bean instanceof SmartLifecycle && bean.isAutoStartup())) {int phase = getPhase(bean);//这个就是说如果map的value已存在 那么就add(beanName, bean)//不存在就创建phases.computeIfAbsent(phase,p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)).add(beanName, bean);if (!phases.isEmpty()) {//这里就执行了 bean.start();//也就是日志:o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''phases.values().forEach(LifecycleGroup::start);}}
}

自动配置

我们经常用的比如改端口

server.prot=8080

是怎么实现的呢?

读取配置

//这个注释意思是如果这个类没有加载 那么ServerProperties这个类也不需要加载了
@EnableConfigurationProperties(ServerProperties.class)
public class ServletWebServerFactoryAutoConfiguration {}@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {private Integer port;...
}

这里就是读取配置
那再哪里修改的呢

修改配置

@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,...}
public class ServletWebServerFactoryAutoConfiguration {}BeanPostProcessorsRegistrar 在 registerBeanDefinitions 方法中注册了WebServerFactoryCustomizerBeanPostProcessorWebServerFactoryCustomizerBeanPostProcessor 的 postProcessBeforeInitialization 方法中遍历所有 WebServerFactoryCustomizer.class 类的Bean : customizer {customizer.customize(webServerFactory);
}

哪里注册了 WebServerFactoryCustomizer ?

public class ServletWebServerFactoryAutoConfiguration {@Beanpublic ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties){...}...
}org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer#customize 中@Override
public void customize(ConfigurableServletWebServerFactory factory) {PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();//存在配置 则调用setPortmap.from(this.serverProperties::getPort).to(factory::setPort);map.from(this.serverProperties::getAddress).to(factory::setAddress);...
}也就是调用了ConfigurableServletWebServerFactory 的setPort

最终读取

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer 中
Tomcat tomcat = new Tomcat();
...
Connector connector = new Connector(this.protocol);
...
customizeConnector(connector);其中customizeConnector(connector)int port = Math.max(getPort(), 0);
connector.setPort(port);其中getPort():
获取属性port 默认是 8080

相关文章:

001-Spring boot 启动内置Web容器分析

目录 代码入口上下文容器 加载web容器WebServercreateWebServergetWebServerFactory()&#xff1a;getWebServer(): 执行WebServer#start自动配置读取配置修改配置 代码入口 上下文容器 SpringApplication.run(App.class); //追踪下去发现 context createApplicationContext…...

【Cocos Creator 项目实战 】消灭星星加强版(附带完整源码工程)

本文乃Siliphen原创&#xff0c;转载请注明出处 目录 概述 游戏整体流程 游戏框架设计 单一职责的类 主要流程控制类 核心玩法模块 UI&#xff1a; 游戏世界&#xff1a; 本文项目的代码组织结构 作者项目实践总结 场景只有一个入口脚本 尽量少在节点上挂载脚本 构…...

2023软件测试岗必问的100个面试题【含答案】

一、测试理论 1.什么是软件测试&#xff1f; 答&#xff1a;软件测试是通过执行预定的步骤和使用指定的数据&#xff0c;以确定软件系统在特定条件下是否满足预期的行为。 2.测试驱动开发&#xff08;TDD&#xff09;是什么&#xff1f; 答&#xff1a;测试驱动开发是一种开…...

MediaExtractor MediaCodec手动解码播放音乐

MediaExtractor MediaCodec手动解码播放音乐,笔记 private class DecodeAudio implements Runnable {Overridepublic void run() {//开始播放pcmaudioTrack.play();MediaExtractor extractor null;MediaCodec codec null;Log.i(TAG, "run: init");FileOutputStrea…...

element表格+表单+表单验证结合运用

目录​​​​​​​ 一、结果展示 二、实现代码 一、结果展示 1、图片 2、描述 table中放form表单&#xff0c;放输入框或下拉框或多选框等&#xff1b; 点击添加按钮&#xff0c;首先验证表单&#xff0c;如果存在没填的就验证提醒&#xff0c;都填了就向下添加一行表单表…...

亚马逊云科技发布Amazon HealthScribe,使用生成式AI技术实现临床文档的自动生成

近日&#xff0c;亚马逊云科技在纽约峰会上推出了Amazon HealthScribe&#xff0c;该服务符合HIPAA&#xff08;《健康保险流通与责任法案》&#xff09;的相关要求&#xff0c;可为医疗软件供应商提供一种基于语音和文字识别的生成式AI技术&#xff0c;帮助其创建能够自动生成…...

Windows11安装Linux子系统,并实现服务自启动,局域网访问,磁盘挂载

Windows11安装Linux子系统&#xff0c;并实现服务自启动&#xff0c;局域网访问&#xff0c;磁盘挂载 一、准备工作二、安装Linux子系统(wsl2)三、为Linux子系统设置桥接网络检查wsl版本在 Hyper-V 管理器中创建虚拟交换机创建 WSL 配置文件启动wsl 四、设置Windows开机自启动L…...

【Git】保姆级详解:Git配置SSH Key(密钥和公钥)到github

博主简介&#xff1a;22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a;是瑶瑶子啦每日一言&#x1f33c;: “当人们做不到一些事情的时候&#xff0c;他们会对你说你也同样不能。”——《当幸福来敲门》 克里斯加德纳 Git配置SSH Key 一、什么是Git?二、什么…...

离线环境conda虚拟环境备份迁移--conda pack问题

1.第一步&#xff1a;创建虚拟环境 conda create -n pyenv --clone base 或者 conda create -n pyenv python3.8.5 --offline 命令执行结束&#xff0c;在路径/xxxx/anaconda/envs 下看到pyenv 或者 conda info --envs 查看罗列虚拟环境 2.第二步&#xff1a;打包环境 conda …...

挂载 IK 分词器至 Elasticsearch Docker 容器 - Docker Docker Compose 教程

简介 本博客将讲解如何在 Docker 和 Docker-Compose 中运行 Elasticsearch&#xff0c;并挂载 IK 分词器。 步骤 一、快速运行Elasticsearch:8.1.3 1.首先&#xff0c;我们需要创建一个新的 Docker 网络&#xff1a;"elastic"。这个网络会提供给我们接下来所要创…...

7.6 通俗易懂解读残差网络ResNet 手撕ResNet

一.举例通俗解释ResNet思想 假设你正在学习如何骑自行车&#xff0c;并且想要骑到一个遥远的目的地。你可以选择直接骑到目的地&#xff0c;也可以选择在途中设置几个“中转站”&#xff0c;每个中转站都会告诉你如何朝着目的地前进。 在传统的神经网络中&#xff0c;就好比只…...

robotframework+selenium 进行webui页面自动化测试

robotframework其实就是一个自动化的框架&#xff0c;想要进行什么样的自动化测试&#xff0c;就需要在这框架上添加相应的库文件&#xff0c;而用于webui页面自动化测试的就是selenium库. 关于robotframework框架的搭建我这里就不说了&#xff0c;今天就给大家根据一个登录的实…...

手机突然无法获取ip地址

在日常生活中&#xff0c;我们对手机的依赖越来越大&#xff0c;尤其是在联网方面。然而&#xff0c;有时候我们可能会遇到手机无法获取IP地址的问题&#xff0c;这给我们的正常使用带来了很多不便。当我们的手机无法获得IP地址时&#xff0c;我们将无法连接到互联网或局域网&a…...

C++——关于命名空间

写c项目时&#xff0c;大家常用到的一句话就是&#xff1a; using namespace std; 怎么具体解析这句话呢&#xff1f; 命名冲突&#xff1a; 在c语言中&#xff0c;我们有变量的命名规范&#xff0c;如果一个变量名或者函数名和某个库里面自带的库函数或者某个关键字重名&…...

怎么进行流程图制作?用这个工具制作很方便

怎么进行流程图制作&#xff1f;流程图是一种非常有用的工具&#xff0c;可以帮助我们更好地理解和展示各种复杂的业务流程和工作流程。它可以将复杂的过程简化为易于理解的图形和文本&#xff0c;使得人们更容易理解和跟踪整个流程。因此&#xff0c;制作流程图是在日常工作中…...

【ChatGPT 指令大全】怎么使用ChatGPT来辅助学习英语

在当今全球化的社会中&#xff0c;英语已成为一门世界性的语言&#xff0c;掌握良好的英语技能对个人和职业发展至关重要。而借助人工智能的力量&#xff0c;ChatGPT为学习者提供了一个有价值的工具&#xff0c;可以在学习过程中提供即时的帮助和反馈。在本文中&#xff0c;我们…...

Ubuntu20配置仅主机网络

Ubuntu20配置仅主机网络&#xff0c;使虚拟机与物理机网络联通且配置固定IP 进入终端&#xff1a;vim /etc/netplan/01-network-manager-all.yaml 修改为&#xff1a; network:ethernets:enp0s8:addresses: [192.168.138.108/24]dhcp4: false optional: truegateway4: 192.…...

调整奇数偶数顺序

调整数组使奇数全部都位于偶数前面。 题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数&#xff0c;来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分&#xff0c;所有偶数位于数组的后半部分。 思路&#xff1a; 1. 给定两个下标left和right&#…...

日志的规范

确定日志级别&#xff1a; 确保你的系统有一个明确的日志级别策略。通常&#xff0c;日志级别包括DEBUG&#xff0c;INFO&#xff0c;WARN&#xff0c;ERROR和FATAL。DEBUG级别的日志记录所有详细信息&#xff0c;适用于开发和调试环境。INFO级别的日志记录常规操作信息&#x…...

Spring AOP(AOP概念,组成成分,实现,原理)

目录 1. 什么是Spring AOP&#xff1f; 2. 为什么要用AOP&#xff1f; 3. AOP该怎么学习&#xff1f; 3.1 AOP的组成 &#xff08;1&#xff09;切面&#xff08;Aspect&#xff09; &#xff08;2&#xff09;连接点&#xff08;join point&#xff09; &#xff08;3&a…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON&#xff0c;依赖模型推理阶段输出进行差分测试&#xff0c;但在训练阶段是不可行的&#xff0c;因为训练阶段直到最后才有固定输出&#xff0c;中间过程是不断变化的。API 库覆盖低&#xff0c;因为各个 API 都是在各种具体场景下使用。…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

SpringAI实战:ChatModel智能对话全解

一、引言&#xff1a;Spring AI 与 Chat Model 的核心价值 &#x1f680; 在 Java 生态中集成大模型能力&#xff0c;Spring AI 提供了高效的解决方案 &#x1f916;。其中 Chat Model 作为核心交互组件&#xff0c;通过标准化接口简化了与大语言模型&#xff08;LLM&#xff0…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...

结构化文件管理实战:实现目录自动创建与归类

手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题&#xff0c;进而引发后续程序异常。使用工具进行标准化操作&#xff0c;能有效降低出错概率。 需要快速整理大量文件的技术用户而言&#xff0c;这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB&#xff0c;…...