JavaWeb 造轮者视角:Spring Boot 启动核心思想与完整链路解析

作者:CodeStats

一个专注分享Java底层原理、自研框架实战干货的技术博主。如果觉得内容实用,欢迎 点赞 + 收藏 + 关注

📚 相关阅读(造轮者必备)

本文的思考,源于我自己动手 手写 Tomcat + 自研 IoC 容器 的经历。如果你也对"造轮子"感兴趣,推荐搭配阅读:

📑 目录

  • 一、一句话说清:Spring Boot 启动就干两件事

  • 二、内嵌 Tomcat 和 DispatcherServlet 如何搭上线

  • 三、核心思想:依赖注入才是灵魂

  • 四、BeanFactoryPostProcessor vs BeanPostProcessor(区别 + 生效时机)

  • 五、12 步 refresh() 的"分段解读"

  • 六、依赖注入什么时候真正发生?

  • 七、事件机制是如何实现的

  • 八、总结:从配置到请求,链路终于通了

一、一句话说清:Spring Boot 启动就干两件事

从造轮者的角度看,Spring Boot 启动时只做两件核心工作:

  1. 构建 IoC 容器:收集所有 Bean 的(BeanDefinition),按规则创建对象并完成依赖注入。

  2. 挂载 Web 能力:启动内嵌 Web 服务器(如 Tomcat),并将请求分发核心 DispatcherServlet 注册进去,让它能把 HTTP 请求转给已经建好的 Controller。

这两件事有严格顺序:先有 IoC 容器,后有 Web 服务器。因为服务器需要的组件(如 Controller、HandlerMapping)都来自容器。

最朴实的比喻:先把家里收拾利索(所有 Bean 造好、依赖引好),再开门营业(启动 Tomcat,接收请求)。

二、内嵌 Tomcat 和 DispatcherServlet 如何搭上线?

2.1 文字说明

这一步是许多人的盲区,其实拆开来看非常简单:

1. Tomcat 是谁启动的?

Spring Boot 在 refresh() 的第 9 步 onRefresh() 中,调用 ServletWebServerApplicationContext#onRefresh()createWebServer() → 实例化 Tomcat,绑定端口,启动。

2. DispatcherServlet 是谁创建的?

DispatcherServletAutoConfiguration 自动配置类中有一个 @Bean 返回 DispatcherServlet 实例,同时还会创建 ServletRegistrationBean(实现了 ServletContextInitializer 接口)。

3. 怎么注册到 Tomcat?

Tomcat 启动后会回调所有 ServletContextInitializerServletRegistrationBean 就把 DispatcherServlet 添加到 Tomcat 的 ServletContext 中,并映射到 "/"

最终效果:http://host:port/xxx → Tomcat → DispatcherServlet → 你的 Controller 方法。

没有魔法:Tomcat 被包装成一个普通 Bean,ServletRegistrationBean 只是一个"注册器"。

三、核心思想:依赖注入才是灵魂

无论看多少遍 Spring 源码,请记住这条主线:

配置信息(注解/配置类) → BeanDefinition(元数据) → Bean 实例 → 依赖注入

三个最关键的组件:

  • BeanDefinition:Bean 的信息,记录类名、作用域、构造参数、依赖关系等。

  • BeanFactoryPostProcessor :这阶段动手脚(修改 BeanDefinition)。例如 ConfigurationClassPostProcessor 解析 @ComponentScan@Import@Bean

  • BeanPostProcessor :在 Bean 实例化之后,对"成品"进行加工(@Autowired 注入、AOP 代理)。

很多初学者混淆后两者,下一节专门对比。

四、BeanFactoryPostProcessor vs BeanPostProcessor(区别 + 生效时机)

接口 操作对象 执行阶段 典型例子
BeanFactoryPostProcessor BeanDefinition(元数据) 实例化任何 Bean 之前refresh() 第 5 步 invokeBeanFactoryPostProcessors ConfigurationClassPostProcessor(解析注解、自动配置)
BeanPostProcessor Bean 实例(已经 new 出来的对象) 实例化之后 ,初始化前后;第 6 步注册,实际调用在 getBean() 过程中 AutowiredAnnotationBeanPostProcessor(处理 @Autowired

一句话总结

BeanFactoryPostProcessor 改图纸,BeanPostProcessor 修成品。

前者在盖楼前改设计图,后者在楼盖好后刷墙布线。

自动配置为什么不用写 <bean>?因为 ConfigurationClassPostProcessor 读到 @SpringBootApplication 后,主动去 META-INF/.../AutoConfiguration.imports 里拉了一堆配置类,全变成了 BeanDefinition

五、12 步 refresh() 的"分段解读"(剥去繁琐,只留骨架)

AbstractApplicationContext.refresh() 共 12 步,分四个阶段理解:

🔹 阶段一:准备 & 获取 BeanFactory(第 1~4 步)

  • prepareRefresh:校验必要属性。

  • obtainFreshBeanFactory创建 DefaultListableBeanFactory(存 BeanDefinition 的仓库)。

  • prepareBeanFactory:配置类加载器、表达式解析器,加几个系统级 BeanPostProcessor。

  • postProcessBeanFactory:扩展钩子(Web 环境注册 Scope)。

产出:一个空的、配置好的 BeanFactory。

🔹 阶段二:加载 & 修改 BeanDefinition(第 5 步)

  • invokeBeanFactoryPostProcessors执行所有 BeanFactoryPostProcessor

    ConfigurationClassPostProcessor 解析主类上的 @ComponentScan@Import@Bean 及自动配置,生成大量新 BeanDefinition 并注册。

产出:beanDefinitionMap 里填满了所有 Bean 的定义信息。

🔹 阶段三:注册"装修队"(第 6 步)

  • registerBeanPostProcessors:找出所有 BeanPostProcessor,实例化并存入工厂列表。

    这些"装修队"会在每个 Bean 实例化后被调用,完成 @Autowired 注入、AOP 等。

注意:只注册,不调用。真正调用在 getBean() 过程中。

🔹 阶段四:基础设施 & 事件 & 实例化(第 7~12 步)

  • initMessageSource:国际化。

  • initApplicationEventMulticaster:事件广播器。

  • onRefresh启动内嵌 Tomcat(见第二部分)。

  • registerListeners:注册事件监听器。

  • finishBeanFactoryInitialization实例化所有非懒加载的单例 Bean,过程中触发 BeanPostProcessor 完成依赖注入。

  • finishRefresh:发布 ContextRefreshedEvent,启动完成。

最核心的是 finishBeanFactoryInitialization ------ 把图纸变成真实对象,并灌入依赖。

六、依赖注入什么时候真正发生?

答案 :在 finishBeanFactoryInitialization() 中调用 getBean() 创建每个单例 Bean 的时候。

简化流程:

  1. getBean(beanName) → 从 beanDefinitionMap 取出 BeanDefinition。

  2. 根据 BeanDefinition 实例化对象(构造器或工厂方法)。

  3. 执行 BeanPostProcessor 的钩子 ,其中 AutowiredAnnotationBeanPostProcessor 扫描 @Autowired 字段/方法,从容器取出依赖并反射赋值。

  4. 执行初始化方法(@PostConstructInitializingBean)。

  5. 返回成品 Bean 存入 singletonObjects 缓存。

依赖注入不是独立的一步,而是嵌在 getBean() 过程中,由 BeanPostProcessor 驱动。

七、事件机制是如何实现的(附简单代码示例)

Spring 的事件机制基于发布-订阅模式,允许 Bean 之间解耦通信。

核心组件

  • 事件(ApplicationEvent) :继承 ApplicationEvent,封装信息。

  • 监听器(ApplicationListener):监听特定事件,执行逻辑。

  • 广播器(ApplicationEventMulticaster):负责派发事件给所有匹配的监听器。

  • 发布者 :注入 ApplicationEventPublisher,调用 publishEvent()

简单代码示例

java

复制代码
// 1. 自定义事件
public class MyCustomEvent extends ApplicationEvent {
    private final String message;
    public MyCustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }
    public String getMessage() { return message; }
}

// 2. 监听器(方式一:实现接口)
@Component
public class MyEventListener implements ApplicationListener<MyCustomEvent> {
    @Override
    public void onApplicationEvent(MyCustomEvent event) {
        System.out.println("收到事件:" + event.getMessage());
    }
}

// 或者方式二:使用 @EventListener(更简洁)
@Component
public class AnotherListener {
    @EventListener
    public void handleEvent(MyCustomEvent event) {
        System.out.println("@EventListener 收到:" + event.getMessage());
    }
}

// 3. 发布事件(任意 Bean 中)
@Service
public class EventPublisherService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void send(String msg) {
        publisher.publishEvent(new MyCustomEvent(this, msg));
    }
}

执行流程

  • Spring 在 initApplicationEventMulticaster() 中创建广播器(默认为 SimpleApplicationEventMulticaster)。

  • registerListeners() 将所有 ApplicationListener Bean 注册到广播器。

  • 调用 publishEvent() 时,广播器同步或异步派发事件给所有监听器。

  • finishRefresh() 最后还会发布 ContextRefreshedEvent,通知容器已启动。

事件机制完全依赖 IoC 容器:监听器本身也是 Bean,广播器从容器获取监听器列表。这正是依赖注入思想的延伸。

八、总结:从配置到请求,链路终于通了

最简化时序链:

text

复制代码
主类 @SpringBootApplication
    ↓ (解析阶段)
ConfigurationClassPostProcessor 读取自动配置、扫描 @Component
    ↓ (生成 BeanDefinition)
beanDefinitionMap 塞满Bean信息
    ↓ (finishBeanFactoryInitialization)
实例化每个 Bean,通过 BeanPostProcessor 完成依赖注入
    ↓ (onRefresh)
Tomcat 启动,DispatcherServlet 注册
    ↓
HTTP 请求 → Tomcat → DispatcherServlet → 你的 Controller

很多人觉得 Spring Boot 复杂,是因为把"自动配置"、"依赖注入"、"Web 服务器启动"、"事件机制"等多件事搅在一起。其实它们是有序串联的:先有 Bean 定义,再有 Bean 实例,再有依赖填充和事件广播器,最后挂上 Web 服务器

如果你能画出这个顺序,再回头看 refresh() 的 12 步,每一步都有它明确的位置和作用。

本文以"造轮者"的视角,只希望用最朴素的逻辑帮你串起整个启动流程。

如果觉得有帮助,别忘了 点赞、收藏、加关注,我是 CodeStats,下篇聊 AOP 到底是怎么"切"进去的

相关推荐
weixin_523185321 小时前
Spring事务为什么会失效?常见场景与解决方案总结
java·数据库·spring
cfm_29141 小时前
JVM对象逃逸分析深度详解
java·开发语言·jvm
云絮.1 小时前
数据库约束
java·数据库·sql·mysql·oracle
weixin_523185321 小时前
SimpleDateFormat为什么线程不安全?源码级解析与解决方案
java·开发语言·安全
Chase_______1 小时前
【Java杂项】Java 中的 null:空指针、自动拆箱与集合边界详解
java·开发语言
程序猿乐锅1 小时前
【JAVASE | 第十九篇】Java 注解入门
java
techdashen1 小时前
Rust 项目管理动态 — 2026 年 2 月
开发语言·后端·rust
Shawn_Shawn1 小时前
Apache Doris Ai Function学习
后端·llm
布朗克1681 小时前
28 网络编程——Socket、TCP/UDP与HttpClient
java·网络·tcp/ip·udp