从零开始解剖Spring Boot启动流程:一个Java小白的奇幻冒险之旅
大家好呀!今天我们要一起探索一个神奇的话题——Spring Boot的启动流程。我知道很多小伙伴一听到"启动流程"四个字就开始头疼,别担心!我会用最通俗易懂的方式,带你从main()方法开始,一步步揭开Spring Boot的神秘面纱,直到内嵌Tomcat欢快地跑起来~ 🏃♂️
准备好了吗?让我们开始这段奇妙的旅程吧!🚀
第一章:一切的开始——main()方法
1.1 那个我们熟悉的入口
每个Spring Boot项目都有一个main()方法,它就像是我们家的前门钥匙🔑,没有它,我们连门都进不去!
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
看到这个@SpringBootApplication注解了吗?它其实是个"套娃"注解,里面包含了三个重要注解:
@SpringBootConfiguration:标识这是个配置类@EnableAutoConfiguration:开启自动配置魔法 ✨@ComponentScan:告诉Spring去哪里找组件
1.2 SpringApplication.run() 做了什么?
当我们调用SpringApplication.run()时,背后发生了很多事情:
- 创建SpringApplication实例:就像组装一台新电脑 💻
- 运行SpringApplication:按下开机键!
// 伪代码简化版
public static ConfigurableApplicationContext run(Class primarySource, String... args) {// 1. 创建SpringApplication实例SpringApplication application = new SpringApplication(primarySource);// 2. 运行!return application.run(args);
}
第二章:SpringApplication的构造过程 �
2.1 确定应用类型
Spring Boot会先判断我们是什么类型的应用:
- Web应用(Servlet):比如用Tomcat
- Reactive Web应用:比如用Netty
- 非Web应用:普通的Java应用
// 判断逻辑大致是这样的
private WebApplicationType deduceWebApplicationType() {if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null) {return WebApplicationType.REACTIVE;}if (ClassUtils.isPresent("javax.servlet.Servlet", null)) {return WebApplicationType.SERVLET;}return WebApplicationType.NONE;
}
2.2 加载初始化器(Initializers)
初始化器就像是Spring Boot的"小助手"们,它们会在应用启动的不同阶段帮忙做一些准备工作。Spring Boot会从META-INF/spring.factories文件中加载这些初始化器。
# 示例 spring.factories 内容
org.springframework.context.ApplicationContextInitializer=\
com.example.MyInitializer,\
com.example.AnotherInitializer
2.3 加载监听器(Listeners)
监听器就像是一群"小耳朵"👂,它们会监听Spring Boot启动过程中的各种事件,比如:
ApplicationStartingEvent:应用刚开始启动ApplicationEnvironmentPreparedEvent:环境准备好了ApplicationPreparedEvent:应用上下文准备好了ApplicationStartedEvent:应用启动完成ApplicationReadyEvent:应用准备就绪
第三章:run()方法的奇幻旅程 🎢
现在,我们来到了最精彩的部分——run()方法的执行过程!让我们一步步来看:
3.1 第一步:启动计时器 ⏱️
Spring Boot一启动就会打开秒表,记录启动耗时:
StopWatch stopWatch = new StopWatch();
stopWatch.start();
3.2 第二步:准备环境 🌍
Spring Boot会准备运行环境,这包括:
- 创建环境对象(根据应用类型不同创建不同的环境)
- 配置环境(读取配置文件,如application.properties/yml)
- 发布
ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(...);
3.3 第三步:创建应用上下文 🏗️
根据应用类型创建不同的应用上下文:
- Web应用:
AnnotationConfigServletWebServerApplicationContext - Reactive Web应用:
AnnotationConfigReactiveWebServerApplicationContext - 非Web应用:
AnnotationConfigApplicationContext
context = createApplicationContext();
3.4 第四步:准备上下文 �
这一步会做很多重要工作:
- 准备环境
- 后置处理Bean定义
- 应用所有初始化器
- 发布
ApplicationContextInitializedEvent事件 - 注册Spring Bean
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
3.5 第五步:刷新上下文 🔄
这是整个启动过程中最复杂的一步!refresh()方法做了很多事情:
refreshContext(context);
让我们深入看看refresh()方法都做了什么:
3.5.1 准备刷新
- 记录启动时间
- 初始化占位符解析器
- 验证必要的属性
3.5.2 获取BeanFactory
- 创建
DefaultListableBeanFactory - 加载Bean定义
3.5.3 准备BeanFactory
- 设置类加载器
- 添加Bean后置处理器
- 注册环境Bean
3.5.4 执行BeanFactory后置处理器
- 执行
BeanFactoryPostProcessor的实现类 - 这里会处理自动配置类(
@EnableAutoConfiguration的核心)
3.5.5 注册Bean后置处理器
- 注册各种
BeanPostProcessor - 这些处理器会在Bean创建前后执行一些逻辑
3.5.6 初始化消息源
- 国际化相关
3.5.7 初始化事件广播器
- 用于发布应用事件
3.5.8 初始化其他特殊Bean
- 比如Tomcat、Jetty等Web服务器
3.5.9 完成BeanFactory初始化
- 初始化所有剩余的单例Bean
3.5.10 完成刷新
- 发布
ContextRefreshedEvent事件 - 启动Web服务器(比如Tomcat)
3.6 第六步:收尾工作 🎬
启动完成后:
- 停止计时器
- 发布
ApplicationStartedEvent和ApplicationReadyEvent事件 - 返回应用上下文
afterRefresh(context, applicationArguments);
stopWatch.stop();
第四章:内嵌Tomcat的启动奥秘 🐱
现在,让我们重点看看内嵌Tomcat是如何启动的!这是很多小伙伴最感兴趣的部分~
4.1 Tomcat在哪里被创建?
在refresh()方法的"初始化其他特殊Bean"阶段,Spring Boot会创建Web服务器。对于Tomcat来说,关键类是这个:
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
4.2 Tomcat启动流程
-
创建Tomcat实例:
Tomcat tomcat = new Tomcat(); -
配置基础信息:
- 设置端口(默认8080)
- 设置上下文路径(默认"/")
- 配置连接器等
-
创建Web应用上下文:
Context context = tomcat.addContext("", docBase); -
配置Servlet容器:
- 添加DispatcherServlet
- 配置过滤器等
-
启动Tomcat:
tomcat.start();
4.3 内嵌Tomcat vs 传统Tomcat
| 特点 | 内嵌Tomcat | 传统Tomcat |
|---|---|---|
| 启动方式 | 通过Java代码启动 | 通过startup.sh/bat脚本启动 |
| 部署方式 | 打包成可执行JAR | 打包成WAR部署到webapps目录 |
| 配置方式 | 通过application.properties配置 | 通过server.xml配置 |
| 类加载器 | 使用应用的类加载器 | 使用Tomcat的类加载器 |
| 版本控制 | 由Spring Boot管理版本 | 需要单独安装和管理 |
4.4 为什么选择内嵌服务器?
- 简化部署:只需要一个JAR文件,不需要额外安装Tomcat
- 版本统一:Spring Boot管理Tomcat版本,避免冲突
- 快速启动:内嵌服务器启动更快
- 微服务友好:适合云原生和容器化部署
- 配置简单:通过配置文件即可完成大部分配置
第五章:自动配置的魔法 ✨
Spring Boot最强大的特性之一就是自动配置,让我们看看它是如何工作的!
5.1 @EnableAutoConfiguration 的秘密
这个注解会导入AutoConfigurationImportSelector,它会从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中加载自动配置类。
5.2 条件注解的妙用
自动配置类通常使用各种条件注解来决定是否生效:
@ConditionalOnClass:当类路径下有指定类时生效@ConditionalOnMissingBean:当容器中没有指定Bean时生效@ConditionalOnProperty:当配置属性满足条件时生效
例如,Tomcat的自动配置类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public class EmbeddedTomcatAutoConfiguration {// 配置代码
}
5.3 自动配置的执行顺序
- 加载所有自动配置类
- 过滤掉不满足条件的配置类
- 按特定顺序应用剩余配置类
- 创建和配置各种Bean
第六章:Spring Boot启动流程图解 🗺️
为了帮助大家更好地理解,我画了一个简化的启动流程图:
main()│├─ 创建SpringApplication实例│ ├─ 确定应用类型│ ├─ 加载初始化器│ └─ 加载监听器│└─ 执行run()方法├─ 启动计时器├─ 准备环境├─ 创建应用上下文├─ 准备上下文├─ 刷新上下文 (核心!)│ ├─ 准备刷新│ ├─ 获取BeanFactory│ ├─ 准备BeanFactory│ ├─ 执行BeanFactory后置处理器│ ├─ 注册Bean后置处理器│ ├─ 初始化消息源│ ├─ 初始化事件广播器│ ├─ 初始化特殊Bean (如Tomcat)│ ├─ 完成BeanFactory初始化│ └─ 完成刷新├─ 收尾工作└─ 返回应用上下文
第七章:常见问题解答 ❓
7.1 为什么我的Spring Boot应用启动很慢?
可能原因:
- 类路径下JAR太多
- 自动配置类太多
- 数据库连接等初始化耗时
- 大量Bean需要初始化
解决方案:
- 使用
@Lazy延迟初始化 - 排除不必要的自动配置
- 减少启动时扫描的包路径
7.2 如何自定义内嵌Tomcat配置?
在application.properties中:
server.port=9090
server.tomcat.max-threads=200
server.tomcat.connection-timeout=5000
或者通过代码:
@Bean
public WebServerFactoryCustomizer tomcatCustomizer() {return factory -> factory.addConnectorCustomizers(connector -> {connector.setPort(9090);connector.setProperty("maxThreads", "200");});
}
7.3 如何查看自动配置的过程?
启动时添加debug参数:
java -jar myapp.jar --debug
或者在application.properties中:
debug=true
第八章:启动优化小技巧 ⚡
8.1 组件扫描优化
默认情况下,Spring Boot会扫描主类所在包及其子包。如果项目很大,可以明确指定扫描路径:
@ComponentScan("com.myapp.package1", "com.myapp.package2")
8.2 延迟初始化
Spring Boot 2.2+支持全局延迟初始化:
spring.main.lazy-initialization=true
8.3 排除自动配置
如果知道某些自动配置不需要,可以排除它们:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
8.4 使用Spring Boot DevTools
在开发环境中,添加DevTools可以加快重启速度:
org.springframework.bootspring-boot-devtoolsruntime
第九章:总结与展望 🏁
哇!我们终于走完了Spring Boot的整个启动流程!让我们回顾一下重点:
- main()方法是入口,调用
SpringApplication.run() - SpringApplication初始化时会确定应用类型、加载初始化和监听器
- run()方法是核心,完成了环境准备、上下文创建和刷新
- 刷新上下文是最复杂的部分,完成了Bean工厂准备、自动配置、Bean注册等
- 内嵌Tomcat在刷新阶段被创建和启动
- 自动配置通过条件注解智能地配置应用
Spring Boot的启动过程就像是一个精密的瑞士手表⌚,各个部件协同工作,最终让我们的应用顺利运行。理解这个过程不仅能帮助我们更好地使用Spring Boot,还能在遇到问题时快速定位原因。
希望这篇文章能帮助你理解Spring Boot的启动机制!如果觉得有用,别忘了点赞收藏哦~ 😊
下次见!👋
推荐阅读文章
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
什么是 Cookie?简单介绍与使用方法
-
什么是 Session?如何应用?
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
如何理解应用 Java 多线程与并发编程?
-
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
如何理解线程安全这个概念?
-
理解 Java 桥接方法
-
Spring 整合嵌入式 Tomcat 容器
-
Tomcat 如何加载 SpringMVC 组件
-
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
-
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
-
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
-
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
-
Java 中消除 If-else 技巧总结
-
线程池的核心参数配置(仅供参考)
-
【人工智能】聊聊Transformer,深度学习的一股清流(13)
-
Java 枚举的几个常用技巧,你可以试着用用
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
-
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)
相关文章:
从零开始解剖Spring Boot启动流程:一个Java小白的奇幻冒险之旅
大家好呀!今天我们要一起探索一个神奇的话题——Spring Boot的启动流程。我知道很多小伙伴一听到"启动流程"四个字就开始头疼,别担心!我会用最通俗易懂的方式,带你从main()方法开始,一步步揭开Spring Boot的…...
集合框架(重点)
1. 什么是集合框架 List有序插入对象,对象可重复 Set无序插入对象,对象不可重复(重复对象插入只会算一个) Map无序插入键值对象,键只唯一,值可多样 (这里的有序无序指的是下标,可…...
IPv4地址分类与常用网络地址详解
常见的 IPv4 地址分类: 1. A 类地址(Class A) 范围:0.0.0.0 到 127.255.255.255 默认子网掩码:255.0.0.0 或 /8 用途:通常用于大型网络,例如大型公司、组织。 特点: 网络地址范围…...
模拟实现memmove,memcpy,memset
目录 前言 一、模拟实现memmove 代码演示: 二、模拟实现memcpy 代码演示: 三、模拟实现memset 代码演示: 总结 前言 这篇文章主要讲解了库函数的模拟实现,包含memmove,memcpy,memset 一、模拟实现m…...
uni-app 开发安卓 您的应用在运行时,向用户索取(定位、相机、存储)等权限,未同步告知权限申请的使用目的,不符合相关法律法规要求
您的应用在运行时,向用户索取(定位、相机、存储)等权限,未同步告知权限申请的使用目的,不符合相关法律法规要求。 测试步骤:1、 工作台 -打卡,申请定位权限;2、工作台-设置-编辑资料-更换头像,申请相机、存 储权限。 修改建议:APP在申请敏感权限时,应同步说明权限申…...
RHCSA Linux 系统文件内容显示2
6. 过滤文件内容显示 grep (1)功能:在指定普通文件中查找并显示含指定字符串的行,也可与管道符连用。 (2)格式:grep 选项... 关键字字符串 文件名... (3)常用选项及说…...
C语言状态字与库函数详解:概念辨析与应用实践
C语言状态字与库函数详解:概念辨析与应用实践 一、状态字与库函数的核心概念区分 在C语言系统编程中,"状态字"和"库函数"是两个经常被混淆但本质完全不同的概念,理解它们的区别是掌握系统编程的基础。 1. 状态字&…...
【2】Kubernetes 架构总览
Kubernetes 架构总览 主节点与工作节点 主节点 Kubernetes 的主节点(Master)是组成集群控制平面的关键部分,负责整个集群的调度、状态管理和决策。控制平面由多个核心组件构成,包括: kube-apiserver:集…...
Redis下载
目录 安装包 1、使用.msi方式安装 2.使用zip方式安装【推荐方式】 添加环境变量 配置后台运行 启动: 1.startup.cmd的文件 2.cmd窗口运行 3.linux源码安装 (1)准备安装环境 (2)上传安装文件 (3&…...
React 文章 分页
删除功能 携带路由参数跳转到新的路由项 const navigate useNavigate() 根据文章ID条件渲染...
OpenCV 图形API(39)图像滤波----同时计算图像在 X 和 Y 方向上的一阶导数函数SobelXY()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::gapi::SobelXY 函数是 OpenCV 的 G-API 模块中用于同时计算图像在 X 和 Y 方向上的一阶导数(即 Sobel 边缘检测)的一…...
传导发射测试(CE)和传导骚扰抗扰度测试(CS)
传导发射测试(CE): 测量接收机: 是EMI测试中最常用的基本测试仪器,仪器类型包括准峰值测量接收机、峰值测量接收机、平均值测量接收机和均方根值测量接收机。测量接收机的几个重要指标分别是:6dB处的带宽、充电时间常数、放电时…...
ubuntu 查看现在服务使用的端口
1. 使用netstat命令 netstat是一个常用的网络工具,可以显示网络连接、路由表、接口统计等信息。虽然在较新的系统中netstat可能被ss命令替代,但仍然可以通过安装net-tools包来使用它。 安装net-tools: sudo apt-get install net-tools 查看…...
即插即用模块(1) -MAFM特征融合
(即插即用模块-特征处理部分) 一、(2024) MAFM&MCM 特征融合特征解码 paper:MAGNet: Multi-scale Awareness and Global fusion Network for RGB-D salient object detection 1. 多尺度感知融合模块 (MAFM) 多尺度感知融合模块 (MAFM) 旨在高效融合 RGB 和深度…...
(学习总结34)Linux 库制作与原理
Linux 库制作与原理 库的概念静态库操作归档文件命令 ar静态库制作静态库使用 动态库动态库制作动态库使用与运行搜索路径问题解决方案方案2:建立同名软链接方案3:使用环境变量 LD_LIBRARY_PATH方案4:ldconfig 方案 使用外部库目标文件ELF 文…...
DSP28335入门学习——第一节:工程项目创建
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.04.20 DSP28335开发板学习——第一节:工程项目创建 前言开发板说明引用解答…...
MDG 实现后端主数据变更后快照自动刷新的相关设置
文章目录 前言实现过程BGRFC期初配置(可选)设置 MDG快照 BGRFC维护BP出站功能模块 监控 前言 众所周知,在MDG变更请求创建的同时,所有reuse模型实体对应的快照snapshot数据都会记录下来。随后在CR中,用户可以修改这些…...
基于瑞芯微RK3562 的四核 AR M Cortex-A53 + 单核 ARM Cortex-M0——MQTT通信方案
前 言 本文主要介绍创龙科技TL3562-MiniEVM评估板基于MQTT通信协议的开发案例,适用开发...
Java 实体类链式操作
目录 1. 使用返回 this 的 setter 方法 2. 使用 Lombok 的 Accessors 注解 3. 建造者模式 (Builder Pattern) 比较 链式设置参数(也称为链式调用或方法链)是一种编程风格,可以让代码更加简洁易读。在 Java 实体类中实现链式设置参数通常有…...
【Linux】Linux 操作系统 - 05 , 软件包管理器和 vim 编辑器的使用 !
文章目录 前言一、软件包管理器1 . 软件安装2 . 包管理器3 . Linux 生态 二、软件安装 、卸载三、vim 的使用1 . 什么是 vim ?2 . vim 多模式3 . 命令模式 - 命令4 . 底行模式 - 命令5. 插入模式6 . 替换模式7 . V-BLOCK 模式8 . 技巧补充 总结 前言 本篇笔者将会对软件包管理…...
【操作系统原理05】存储器管理
大纲 文章目录 大纲一. 内存基础知识0.大纲1.什么是内存2.进程运行基本原理2.1 指令工作原理2.2逻辑地址VS物理地址2.3 从写程序到程序运行完整运行三种链接方式 二.内存管理0.大纲1.操作系统进行内存管理 三.覆盖与交换0.大纲1.覆盖技术2.交换技术 四.连续分配管理方式0.大纲1…...
学习笔记—C++—string(练习题)
练习题 仅仅反转字母 917. 仅仅反转字母 - 力扣(LeetCode) 题目 给你一个字符串 s ,根据下述规则反转字符串: 所有非英文字母保留在原有位置。所有英文字母(小写或大写)位置反转。 返回反转后的 s 。…...
[Swift]Xcode模拟器无法请求http接口问题
1.以前偷懒一直是这样设置 <key>NSAppTransportSecurity</key> <dict><key>NSAllowsArbitraryLoads</key><true/><key>NSAllowsArbitraryLoadsInWebContent</key><true/> </dict> 现在我在Xcode16.3上ÿ…...
返回之术:用 navigate(-1) 闯荡前端江湖
前言 在前端这片江湖,页面跳转宛如轻功水上漂,来去无踪,飘忽不定。但其中有一门绝学,专治“回头是岸”之需求,那便是 React Router 中的 navigate(-1) 身法。 昔日我闯荡项目林,误入“下一页”禁地,一脚踏空,身陷页面迷阵。正当我焦头烂额之际,师父袖袍一挥,口吐一…...
《Operating System Concepts》阅读笔记:p748-p748
《Operating System Concepts》学习第 64 天,p748-p748 总结,总计 1 页。 一、技术总结 1.Transmission Control Protocol(TCP) 重点是要自己能画出其过程,这里就不赘述了。 二、英语总结(生词:3) transfer, transport, tran…...
基于深度学习的线性预测:创新应用与挑战
一、引言 1.1 研究背景 深度学习作为人工智能领域的重要分支,近年来在各个领域都取得了显著的进展。在线性预测领域,深度学习也逐渐兴起并展现出强大的潜力。传统的线性预测方法在处理复杂数据和动态变化的情况时往往存在一定的局限性。而深度学习凭借…...
网络编程3
day3 一、服务器模型 1.循环服务器模型 同一个时刻只能响应一个客户端的请求 2.并发服务器模型 2.1含义 同一个时刻可以响应多个客户端的请求,常用的模型有多进程模型/多线程模型/IO多路复用模型。 2.2多进程模型 每来一个客户端连接,开一个子进程来专门…...
数字化时代下的工业物联网智能体开发平台策略
1. 引言 1.1 工业物联网智能体的发展背景 随着工业4.0的兴起和数字化转型的不断深入,工业物联网(IIoT)已成为推动制造业创新发展的关键技术之一。智能体作为工业物联网的核心组成部分,其开发平台的建设与应用对于实现智能化升级、提升生产效率、降低…...
[Java实战经验]异常处理最佳实践
一些好的异常处理实践。 目录 异常设计自定义异常为异常设计错误代码(状态码)设计粒度全局异常处理异常日志信息保留 异常处理时机资源管理try-with-resources异常中的事务 异常设计 自定义异常 自定义异常设计,如业务异常定义BusinessExce…...
海拔与大气压关系,大气压单位,气压传感器对比
mbmbar 毫巴(百帕) mbar 毫巴(百帕) hPa 百帕 1百帕1毫巴3/4毫米水银柱 1Kpa10百帕7.5毫米汞柱7.5mmhg 1Bar0.1MPa1000mba1000hpa100*7.5mmhg75mmhg1个大气压 HP303B HP303S HP203N BMP280...
