SpringBoot项目的启动
Spring是什么Spring 是一个 Java 框架。它解决的核心问题是Java 代码里到处都是 new 对象导致类和类之间紧紧耦合在一起改一个地方要动十个地方IoC控制反转是什么publicclassOrderService{// 不自己 new由外部注入进来AutowiredprivateUserDaouserDao;}「控制反转」 把「创建对象的控制权」从你的代码反转给 Spring 框架。 Spring 帮你 new、帮你组装、帮你管理这些对象的生死Bean是什么被 Spring 管理的对象就叫 Bean记住Bean 交给 Spring 创建和管理的 Java 对象Component// ← 加上这个Spring 就会创建一个 UserService 的 BeanpublicclassUserService{}Service// 其实 Service 内部就是 Component只是语义更清晰publicclassOrderService{}容器ApplicationContext是什么容器 一个装满 Bean 的大盒子 一堆管理这些 Bean 的能力负责扫描类、创建 Bean处理 Bean 和 Bean 之间的依赖关系管理 Bean 的生命周期创建 → 初始化 → 销毁处理配置文件、事件、国际化、AOP 等Spring Boot 启动的本质就是把这个容器造出来、填满、然后启动起来从main方法看起SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}}SpringBootApplication其实是三个注解叠在一起的Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)DocumentedInheritedSpringBootConfiguration// ①EnableAutoConfiguration// ②ComponentScan// ③publicinterfaceSpringBootApplication{// 一些过滤属性比如 exclude、scanBasePackages}SpringBootConfigurationEnableAutoConfigurationComponentScanSpringBootConfigurationConfiguration// ← 它的本体就是这个publicinterfaceSpringBootConfiguration{}其实就是configuration作用标记这个类是一个配置类Spring 需要知道每个对象该怎么造出来配置类就是告诉它这件事的地方Configuration// 告诉 Spring下面这个类是说明书照着它造对象publicclassMyConfig{Bean// 告诉 Spring这个方法是「一条制造配方」publicDataSourcedataSource(){returnnewDruidDataSource();}}Configuration把 MyConfig 标记为配置类。Spring 启动时会读取这个类里的所有Bean 方法Bean修饰一个方法意思是执行这个方法把返回的对象放进容器方法名 dataSource → 默认就是这个 Bean 的名字返回类型 DataSource → 就是这个 Bean 的类型return new DruidDataSource() → 这就是实际造出来的对象于是容器里就有了一个 DataSource 类型的 BeanComponentScan组件扫描作用告诉 Spring 去指定的包下面扫描把带Component/Service/Repository/Controller 的类都注册成 BeanEnableAutoConfiguration自动配置解决的痛点假设没有自动配置你想在项目里用 Redis。你要做的事ConfigurationpublicclassRedisConfig{BeanpublicRedisConnectionFactoryredisConnectionFactory(){LettuceConnectionFactoryfnewLettuceConnectionFactory();f.setHostName(localhost);f.setPort(6379);// 连接池、序列化器、超时时间……几十行配置returnf;}BeanpublicRedisTemplateString,ObjectredisTemplate(){...}}用 MyBatis 再写一堆用 Druid 再写一堆……每个第三方组件都要自己写样板代码而 Spring Boot 的目标是加一个依赖就直接能用dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency依赖一加RedisTemplate 就自动出现在容器里Autowired 直接用Spring Boot 在它的 jar 包里提前替你把配置类写好了。EnableAutoConfiguration 的职责就是把这些预写好的配置类找出来、批量注册回到代码Import(AutoConfigurationImportSelector.class)// ← 关键publicinterfaceEnableAutoConfiguration{}Import 会把 AutoConfigurationImportSelector 这个类「导入」进来Spring 会调用它的 selectImports() 方法让它返回一大堆需要被注册的配置类这些类名再jar包内名单里的类内部是如何的ConfigurationConditionalOnClass(RedisOperations.class)// ← 条件classpath 里有 Redis 相关类才生效EnableConfigurationProperties(RedisProperties.class)// ← 绑定 application.yml 里的配置publicclassRedisAutoConfiguration{BeanConditionalOnMissingBean(nameredisTemplate)// ← 条件容器里还没有才造publicRedisTemplateObject,ObjectredisTemplate(RedisConnectionFactoryfactory){RedisTemplateObject,ObjecttnewRedisTemplate();t.setConnectionFactory(factory);returnt;}}那为什么不是把所有东西都注册为Bean呢ConditionalOnClass你引了 redis 依赖才有 Redis 相关类 → 才配置 RedisConditionalOnMissingBean你自己写了 Bean RedisTemplate → Spring 就不造了ConditionalOnProperty配置文件里某属性满足才生效spring.redis.enabledtrue启动的一系列流程你写的 main() 方法 │ ▼ SpringApplication.run() │ ├── 1. 创建 SpringApplication 对象推断应用类型、加载监听器 ├── 2. 发布 ApplicationStartingEvent ├── 3. 准备 Environment读 application.yml、命令行参数、环境变量 ├── 4. 打印 Banner那个大大的 SPRING 图 ├── 5. 创建 IoC 容器 ApplicationContext ├── 6. 准备容器注入环境、发布 ApplicationContextInitializedEvent ├── 7. 加载配置类你的 SpringBootApplication 类 ├── 8. ★ refresh() ★ ←— 整个启动最核心的一步 │ ├── 解析 ComponentScan、Import │ ├── ★ 自动配置生效几百个 AutoConfiguration 被条件过滤★ │ ├── 创建并启动内嵌 Tomcat │ └── 实例化所有单例 Bean ├── 9. 发布 ApplicationStartedEvent ├── 10. 执行 CommandLineRunner / ApplicationRunner └── 11. 发布 ApplicationReadyEvent → 应用可用启动分为new SpringApplication()步骤和run()步骤new SpringApplication() 阶段不启动任何容器、不扫描任何 Bean它只是在填表格——登记三件事我是谁primarySources / mainClass、我运行在什么环境webApplicationType、我带了哪些扩展initializers / listeners真正的启动动作全在后面的 run() 里先看一下run()的源码publicstaticConfigurableApplicationContextrun(Class?primarySource,String...args){returnrun(newClass?[]{primarySource},args);}publicstaticConfigurableApplicationContextrun(Class?[]primarySources,String[]args){// ① 先创建 SpringApplication 对象returnnewSpringApplication(primarySources).run(args);}run()就是先new然后再调用run()new SpringApplication()干的事情我是谁primarySources / mainClass我运行在什么环境webApplicationType我带了哪些扩展initializers / listenersSpringApplication 的构造函数不启动任何东西它只是把你希望 Spring 加载哪些类/资源登记到一张去重、保序的名单里primarySources等 run() 真正启动时才把这张名单拿出来用我是谁primarySources / mainClassthis.primarySourcesnewLinkedHashSet(Arrays.asList(primarySources));用LinkedHashSet去重run(DemoApp.class, OtherConfig.class, DemoApp.class) 里 DemoApp 传了两次只该注册一次。写成 new ArrayList(…) 就会重复注册保序多个配置类注册成 BeanDefinition 的先后顺序是确定的方便排查问题、也避免不同 JVM 运行结果不一样。写成 HashSet 顺序就随哈希值走了传的是一个.Class不是一个对象我运行在什么环境webApplicationType// 推断应用类型SERVLET / REACTIVE / NONE ★this.webApplicationTypeWebApplicationType.deduceFromClasspath();WebApplicationType.deduceFromClasspath() 的逻辑很简单看 classpath 上有什么类检查条件判定结果对应容器DispatcherHandler存在 且DispatcherServlet不存在REACTIVEWebFluxAnnotationConfigReactiveWebServerApplicationContextServlet 相关类存在SERVLET普通 WebAnnotationConfigServletWebServerApplicationContext都不满足NONE普通 Java 应用AnnotationConfigApplicationContext我带了哪些扩展initializers / listeners//加载 ApplicationContextInitializer从 spring.factoriessetInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class));// 加载 ApplicationListener从 spring.factoriessetListeners(getSpringFactoriesInstances(ApplicationListener.class));run()干的事情简化的源码publicConfigurableApplicationContextrun(String...args){// 计时器最后打印 Started DemoApplication in 2.345 secondsStopWatchstopWatchnewStopWatch();stopWatch.start();ConfigurableApplicationContextcontextnull;CollectionSpringApplicationRunListenerlistenersgetRunListeners();try{// ─── 1. 准备启动参数 ───ApplicationArgumentsapplicationArgumentsnewDefaultApplicationArguments(args);// ─── 2. 准备 Environment配置环境───ConfigurableEnvironmentenvironmentprepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);// ─── 3. 打印 Banner ───BannerprintedBannerprintBanner(environment);// ─── 4. ★ 创建 IoC 容器 ★ ───contextcreateApplicationContext();exceptionReportersgetSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{ConfigurableApplicationContext.class},context);// ─── 5. 准备容器 ───prepareContext(context,environment,listeners,applicationArguments,printedBanner);// ─── 6. ★★★ 刷新容器最核心★★★ ───refreshContext(context);// ─── 7. 刷新后置处理空实现留给子类───afterRefresh(context,applicationArguments);stopWatch.stop();if(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);}// ─── 8. 发布 ApplicationStartedEvent ───listeners.started(context);// ─── 9. 执行 Runner ───callRunners(context,applicationArguments);}catch(Throwableex){handleRunFailure(context,ex,listeners);thrownewIllegalStateException(ex);}// ─── 10. 发布 ApplicationReadyEvent应用正式可用 ───listeners.running(context);returncontext;}变成人话就是开始计时 ↓ 告诉所有监听器我要开始启动了 → ApplicationStartingEvent ↓ 读配置文件、读环境变量、读命令行参数 → ApplicationEnvironmentPreparedEvent ↓ 打印 Banner ↓ 创建一个空容器此时容器里还没有 Bean ↓ 往容器里塞环境对象、主配置类 ↓ ★ 刷新容器 ★在这里把 Bean 全部创建出来Tomcat 也在这里启动 ↓ 停止计时打印启动耗时 ↓ 告诉监听器容器准备好了 → ApplicationStartedEvent ↓ 执行 CommandLineRunner / ApplicationRunner ↓ 告诉监听器应用可以接客了 → ApplicationReadyEvent配置是怎么被读进来的privateConfigurableEnvironmentprepareEnvironment(SpringApplicationRunListenerlisteners,ApplicationArgumentsapplicationArguments){// 1. 根据应用类型创建 Environment 对象ConfigurableEnvironmentenvironmentgetOrCreateEnvironment();// 2. 把命令行参数--server.port8081放进 EnvironmentconfigureEnvironment(environment,applicationArguments.getSourceArgs());// 3. ★ 通知监听器环境准备好了 ★// 这里会发布 ApplicationEnvironmentPreparedEvent// ↓// ConfigFileApplicationListenerBoot 2.3 之前/// ConfigDataEnvironmentPostProcessorBoot 2.4会在这里被触发// ↓// 它负责读取 application.yml / application.properties / bootstrap.ymllisteners.environmentPrepared(environment);...returnenvironment;}Environment 里的配置来源有优先级命令行参数 --server.port8081 ↑ 高于 SPRING_APPLICATION_JSON 环境变量 ↑ 高于 系统属性 System.getProperties() ↑ 高于 环境变量如 SERVER_PORT8081 ↑ 高于 application-{profile}.yml ↑ 高于 application.yml ↑ 高于 PropertySource 注解 ↑ 高于 默认属性容器怎么被选出来的

相关新闻

深度学习图像识别系统开发实战:从数据准备到模型部署

深度学习图像识别系统开发实战:从数据准备到模型部署

技术博客 Python PyTorch torchvision ONNX Runtime 在人工智能快速发展的今天,图像识别已经不是什么遥不可及的黑科技。从手机相册自动识别人脸,到工厂流水线检测产品缺陷,再到智能安防、自动驾驶,背后都离不开计算机视觉和…

2026/10/11 11:47:41 阅读更多 →
生成式引擎优化(GEO)的范式重构:AI入口商业化下的价值迁移与落地路径

生成式引擎优化(GEO)的范式重构:AI入口商业化下的价值迁移与落地路径

AI 入口商业化之后,GEO 是死了,还是换了形态【摘要】拆解AI回答的4层决策机制,对比2类主流AI入口的商业化演进路径,梳理6项可落地的新GEO执行动作,明确旧GEO模式的3类失效边界,论证商业化进程中信任资产的溢…

2026/10/10 6:19:52 阅读更多 →
Matlab同步相量测量算法对比:FFT、窗函数、HHT与小波变换的工程实现

Matlab同步相量测量算法对比:FFT、窗函数、HHT与小波变换的工程实现

同步相量测量这事儿,这几年在电力系统里越来越绕不开。从广域测量系统(WAMS)到新能源场站的控制策略,再到故障录波分析,几乎每个环节都要跟“相量”打交道。但真正在Matlab里动手算过的人都知道,把一个正弦…

2026/10/10 6:19:52 阅读更多 →

最新新闻

n8n实战:用可视化节点搭建自动化工作流,告别脚本泥潭

n8n实战:用可视化节点搭建自动化工作流,告别脚本泥潭

第一次接触 n8n,是因为被一堆手工报表逼到崩溃。那阵子我每天早上的固定动作是打开三个后台系统,把销售数据、库存余量和客服反馈分别导出,再手动整理成一张总表发出去,整个过程雷打不动要花掉二十分钟,错一位小数都不…

2026/10/11 12:46:35 阅读更多 →
C# WinForms工控上位机界面开发:从串口数据到自定义控件的实战方案

C# WinForms工控上位机界面开发:从串口数据到自定义控件的实战方案

简介:《C# WinForm高级设计(工控与界面)》是一份面向C#桌面开发者与工控行业从业者的进阶学习资源,聚焦WinForm在工业监控与用户界面设计中的实践应用,系统讲解控件布局、事件处理、数据绑定、多线程通信及硬件数据交换…

2026/10/11 12:46:35 阅读更多 →
AI时代网站建设新标准:从传统SEO到GEO的转型实践

AI时代网站建设新标准:从传统SEO到GEO的转型实践

1. AI时代网站流量格局变了,传统SEO正在失效这两年做网站的人应该都有明显感觉:以前辛辛苦苦做SEO,把关键词排名做到搜索引擎前几页,流量还比较稳定。但现在,越来越多人获取信息的入口变了。很多人遇到问题&#xff0c…

2026/10/11 12:46:35 阅读更多 →
大小核调度实操指南:三步强制绑定大核,让性能核不再闲置

大小核调度实操指南:三步强制绑定大核,让性能核不再闲置

用过混合架构处理器的朋友应该都有过这种体验:明明买的是高性能旗舰,结果打开某个老软件或大型游戏时,画面还是时不时卡一下,打开任务管理器一看,程序居然被系统安排在了一堆小核上,大核在旁边闲着。这不是…

2026/10/11 12:46:35 阅读更多 →
超翔TZ830-V3重装Win7驱动指南:硬件ID定位与三步安装法

超翔TZ830-V3重装Win7驱动指南:硬件ID定位与三步安装法

简介:这是清华同方超翔TZ830-V3国产化台式机在Win7 64位旗舰版下的驱动合集,适用于使用兆芯KX-U6780A处理器、THTF主板及Radeon R5 430显卡的用户,解决重装系统后网卡、声卡、显卡等硬件驱动缺失问题。压缩包共256个文件,包含dll动…

2026/10/11 12:46:35 阅读更多 →
python深入讲解魔术方法

python深入讲解魔术方法

前言 魔术方法(magic methods)就是那些名字两头带双下划线、由 Python 语法隐式调用的特殊方法,比如 __init__、__repr__、__eq__。它们不是给你手动调的接口,而是给语言本身看的钩子:你写了 __len__,len(o…

2026/10/11 12:45:34 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →