在AI的帮助下理解spring的启动过程

spring的启动过程

面试的时候怎么说?

首先要明确的一点是spring的启动过程是围绕bean进行的。再然后是spring是个容器。所以首先我们要创建这个容器。也就是applicationContext,这是个接口。通常我们在启动类中会有相关的实现类,比如说

复制代码
new AnnotationConfigApplicationContext(AppConfig.class);
// 或
new ClassPathXmlApplicationContext("application.xml");

这里也把我们的配置文件给加载了进来。

现在容器有了,这时候就进入到源码中spring核心的refresh()方法。容器是用来放bean的。那就需要创建bean。怎么创建呢?spring是通过BeanFactory生产bean的。那就需要创建beanFactory。

factory有了后就需要加载bean了。但是要知道去哪找找bean。这时候就需要通过比如@ComponentScan等方式扫描指定文件,生成beanDefinition,也就是bean的公共定义,像类的名称、作用域、是否懒加载、依赖的bean等。加载完了后,就开始实例化bean、属性填充、初始化。bean都创建完了,spring也就启动完了。

这就是spring的启动流程。

整体流程启动完后,再补充下几点细节

各个阶段预留了那些接口

其实对于开发者来说,我们怎么能在spring启动过程中插入我们自己的操作呢?也就是spring有预留给开发者哪些接口呢?这里统计以下,基本上每个阶段都有接口供我们个性化操作。

bash 复制代码
| 接口                                            | 介入时机                         | 用途                                                                      |
| --------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------- |
| **`ApplicationContextInitializer`**           | 容器 `refresh()` 之前            | 在容器刷新前修改 `Environment` 或容器状态                                            |
| **`BeanFactoryPostProcessor`**                | BeanDefinition 加载后,Bean 实例化前 | 修改 BeanDefinition(如 `PropertySourcesPlaceholderConfigurer` 处理 `@Value`) |
| **`BeanPostProcessor`**                       | Bean 初始化前后                   | 干预 Bean 创建(AOP、@Autowired、@PostConstruct 都基于此)                          |
| **`InstantiationAwareBeanPostProcessor`**     | Bean 实例化前后                   | 甚至可以自定义返回代理实例替代正常实例化                                                    |
| **`Aware` 接口族**                               | 属性填充后,初始化前                   | 注入容器基础设施(`BeanFactoryAware`、`ApplicationContextAware`)                  |
| **`InitializingBean` / `@PostConstruct`**     | 属性填充后                        | 自定义初始化逻辑                                                                |
| **`DisposableBean` / `@PreDestroy`**          | 容器关闭时                        | 自定义销毁逻辑                                                                 |
| **`ApplicationListener`**                     | 事件发生时                        | 监听 `ContextRefreshedEvent` 等                                            |
| **`ApplicationRunner` / `CommandLineRunner`** | 容器完全就绪后                      | 执行启动后业务任务                                                               |

其中为我们经常最关注的是beanPostProcessor。这里能够进行AOP前面操作,还有常用的@PostConstruct 是在初始化的时候操作的,基本上实在bean创建完成时执行的了。

下面是bean实例化过程的步骤,记住先进行属性填充再初始化。
bash 复制代码
1. 从 singletonObjects 缓存中查找
   ↓ 未找到
2. 标记该 Bean 正在创建中(解决循环依赖的关键)
   ↓
3. 合并 BeanDefinition(处理父子 BeanDefinition)
   ↓
4. 检查依赖的 Bean,递归 getBean()
   ↓
5. 【createBean()】
   ├── 5.1 实例化(调用构造函数):`createBeanInstance()`
   ├── 5.2 属性填充:`populateBean()` → 执行 @Autowired 注入
   └── 5.3 初始化:`initializeBean()`
           ├── 执行 Aware 接口回调(BeanNameAware、ApplicationContextAware)
           ├── 执行 BeanPostProcessor.postProcessBeforeInitialization()
           ├── 执行 @PostConstruct / InitializingBean.afterPropertiesSet()
           └── 执行 BeanPostProcessor.postProcessAfterInitialization()
   ↓
6. 放入 singletonObjects 缓存
用到了哪些设计模式
  • 模板方法模式refresh() 定义了固定启动步骤,具体某些步骤由子类实现(如 onRefresh() 启动 Web 服务器)。就像开业流程固定,但"迎宾方式"可由各餐厅自己定。

  • 工厂模式BeanFactoryApplicationContext 就是生产 Bean 的工厂

  • 单例模式:Spring 管理的 Bean 默认是单例

  • 观察者模式ApplicationEvent + ApplicationListener 启动过程有很多event类,都是监听器。

  • 代理模式:AOP 的核心。三级缓存里提前暴露的半成品,就是用动态代理包了一层,实现事务、日志等功能

三级缓存

三级缓存讲了很多遍了,但是总感觉差点意思。问了下kimi.回答的真好

主要点是如果只是解决循环依赖,二级缓存也可以。但是如果要用到aop代理就必须要用到三级缓存了。因为A完成实例化后的对象在一级缓存中是A的proxy_A,但是B引用的还是A的真实对象。导致AOP失效。像使用了@Async\@Transactional的都是需要aop的类。

怎么解决aop失效的问题呢?就是在三级缓存时,调用getEarlyBeanReference方法,aop发现A需要代理,就提前生成a_proxy,把代理后的类放到B中。总之,是通过判断按需处理的。

Spring 三级缓存与代理类 ------ 深度问答


Q1:三级缓存是哪三级?分别存了什么?

表格

缓存名称 级别 存储内容 代码位置
singletonObjects 一级缓存 成品 Bean(已实例化、已注入、已初始化) ConcurrentHashMap<String, Object>
earlySingletonObjects 二级缓存 早期暴露的 Bean(已实例化,但未完成属性注入和初始化) ConcurrentHashMap<String, Object>
singletonFactories 三级缓存 ObjectFactory 函数式接口(一个"工厂",调用后返回早期 Bean 引用) HashMap<String, ObjectFactory<?>>

核心设计思想:一级缓存存成品,二级缓存存半成品,三级缓存存"能生成半成品的工厂"。


Q2:三级缓存是在什么时候被使用的?(循环依赖场景)

假设 A 依赖 B,B 又依赖 A:

plain

复制代码
创建 A
  ├── 实例化 A(调用构造函数,此时 A 是个空壳,属性未注入)
  ├── 将 A 的 ObjectFactory 放入三级缓存(提前暴露)
  ├── 属性填充:发现需要 B → 开始创建 B
  │       ├── 实例化 B
  │       ├── 属性填充:发现需要 A → 从缓存找 A
  │       │       ├── 一级缓存:没有(A 还没初始化完)
  │       │       ├── 二级缓存:没有
  │       │       └── 三级缓存:有!调用 ObjectFactory.getObject() 拿到早期 A 引用
  │       │               └── 将早期 A 放入二级缓存,清空三级缓存
  │       ├── 继续 B 的初始化(此时 B 里的 A 是早期引用)
  │       └── B 创建完成,放入一级缓存
  ├── 回到 A,继续属性填充(B 已就绪)
  ├── A 初始化完成
  └── A 放入一级缓存

Q3:如果只是解决循环依赖,两级缓存(一级 + 二级)够吗?

够,但前提是:没有 AOP 代理。

如果没有代理,实例化后直接把原始对象放入二级缓存即可,B 注入的就是这个原始对象,等 A 初始化完成后再放入一级缓存。

但有了 AOP 代理后,两级缓存就出问题了。


Q4:代理类为什么会让两级缓存失效?问题出在哪?

核心矛盾 :AOP 代理是在 Bean 初始化之后 才生成的(BeanPostProcessor.postProcessAfterInitialization())。

场景:A 被 AOP 代理,B 循环依赖 A

plain

复制代码
时间线:
T1: A 实例化(原始对象 A_raw)
T2: A 属性填充 → 需要 B → 创建 B
T3: B 属性填充 → 需要 A → 从二级缓存拿 A_raw(此时 A 还没初始化)
T4: B 创建完成
T5: 回到 A,A 初始化 → AOP 介入 → 生成代理对象 A_proxy
T6: A 放入一级缓存(存的是 A_proxy)

问题:B 中注入的是 A_raw(原始对象),但一级缓存中最终是 A_proxy(代理对象)
结果:B 里的 A 不是代理对象,AOP 失效!

这就是"早期暴露原始对象 vs 后期生成代理对象"的冲突。


Q5:三级缓存是怎么解决"循环依赖 + AOP 代理"问题的?

三级缓存存的不是对象,而是 ObjectFactory(工厂)

java

复制代码
// 实例化后,放入三级缓存的代码(AbstractAutowireCapableBeanFactory)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference() 是关键:

java

复制代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    // 遍历所有 SmartInstantiationAwareBeanPostProcessor
    // 其中就包括 AnnotationAwareAspectJAutoProxyCreator(AOP 处理器)
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            // 如果该 Bean 需要被代理,这里就会提前生成代理对象!
            exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp)
                              .getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

流程变化:

plain

复制代码
T1: A 实例化(A_raw)
T2: 将 () -> getEarlyBeanReference("A", mbd, A_raw) 放入三级缓存
T3: A 属性填充 → 需要 B → 创建 B
T4: B 属性填充 → 需要 A
        └── 调用三级缓存的 ObjectFactory.getObject()
            └── 触发 getEarlyBeanReference()
                └── AOP 处理器发现 A 需要代理 → 提前生成 A_proxy
                └── 返回 A_proxy 给 B 注入
T5: B 创建完成
T6: 回到 A,继续初始化
        └── 初始化完成后,postProcessAfterInitialization() 再次检查 AOP
        └── 但 AOP 处理器发现 A 已经代理过了(earlyProxyReferences 中有记录)
        └── 直接返回原始对象(不做二次代理)
T7: A 放入一级缓存

结果:B 注入的是 A_proxy,一级缓存存的也是 A_proxy,完全一致!


Q6:代理类在三级缓存中的具体作用是什么?

表格

作用 说明
延迟生成代理 三级缓存存的是工厂,不是对象。只有在真正发生循环依赖、需要提前暴露时,才触发代理生成。如果没有循环依赖,代理仍按正常流程在初始化后生成。
保证单例唯一 通过 earlyProxyReferences 集合记录哪些 Bean 已经提前代理了,避免初始化后重复生成代理。
解耦实例化与代理 实例化阶段不直接生成代理(因为属性还没注入),但工厂可以在被调用时"按需"生成代理。
打破时间差 解决了"循环依赖需要早期引用"和"AOP 代理需要后置处理"之间的时间差矛盾。

Q7:为什么三级缓存的工厂只调用一次?

java

复制代码
// DefaultSingletonBeanRegistry.getSingleton()
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);      // 查一级
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);  // 查二级
        if (singletonObject == null && allowEarlyReference) {
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 查三级
            if (singletonFactory != null) {
                singletonObject = singletonFactory.getObject();        // 调用工厂
                this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级
                this.singletonFactories.remove(beanName);              // 清空三级
            }
        }
    }
    return singletonObject;
}

关键动作:一旦调用工厂拿到对象,立即:

  1. 将结果放入 二级缓存

  2. 三级缓存移除

这保证了:

  • 同一个 Bean 的早期引用始终只有一个

  • 后续再需要该 Bean,直接从二级缓存拿,不会重复生成代理


Q8:如果 A 和 B 互相依赖,且都需要代理,会生成几个代理对象?

答案:各一个,且都是正确的代理对象。

plain

复制代码
创建 A_proxy
  ├── A 实例化
  ├── A 放入三级缓存(工厂)
  ├── A 属性填充 → 需要 B → 创建 B
  │       ├── B 实例化
  │       ├── B 放入三级缓存(工厂)
  │       ├── B 属性填充 → 需要 A
  │       │       └── 调用 A 的工厂 → 生成 A_proxy(如果 A 需要代理)
  │       │       └── B 注入 A_proxy
  │       ├── B 初始化 → 生成 B_proxy(如果 B 需要代理)
  │       └── B 完成,放入一级缓存(B_proxy)
  ├── A 属性填充(拿到 B_proxy)
  ├── A 初始化 → AOP 发现已提前代理,跳过
  └── A 完成,放入一级缓存(A_proxy)

两个代理都在各自需要提前暴露时(或初始化后)正确生成,且只生成一次。


Q9:Spring 为什么不允许构造器循环依赖?

因为三级缓存的介入时机是在"实例化之后"。

java

复制代码
// 循环依赖:A 的构造器需要 B,B 的构造器需要 A
创建 A
  ├── 调用 A 的构造函数 → 需要 B
  │       ├── 调用 B 的构造函数 → 需要 A
  │       │       └── A 还没实例化完,三级缓存还没有 A 的工厂!
  │       └── 死循环 / 报错

解决方式:

  • @Lazy 延迟注入:@Lazy private B b;(注入的是代理占位符,真正使用时才创建)

  • 改用 Setter 注入或字段注入


Q10:一句话总结三级缓存与代理的关系

三级缓存的本质是"延迟代理"策略:通过 ObjectFactory 将代理对象的生成时机从"初始化后"推迟到"首次被循环依赖需要时",从而保证注入到其他 Bean 中的早期引用和最终成品是同一个代理对象。

springboot的启动

其实就是以spring为中心,前面加上对服务类型的判断,比如是否是web,然后加载不同环境的配置application。再者就是启动快结束时利用spring预留的钩子onRefresh()创建并启动内嵌的tomcat服务器,并把dispatcherServlet等关键的web bean注册到servlet容器中。

当前了标识springboot最关键的还是自动配置。

自动配置:其本质是额外的配置类。在加载bean的阶段,会到各个包的spring.factories文件中批量导入自动配置类。

springcloud的启动

springcloud是由一个个小的springboot组成的,把这些微服务放到一个框架中,大家共用一套配置。所以多了很多配置中心、注册中心的配置。启动时,会先有次序的加载环境配置。

bean的加载顺序

我们不用管,容器会根据bean之间的注入关系自动按顺序加载。

为啥要用构造器注入

构造器注入不解决训练依赖问题,会在启动时报错的。

"构造器注入是最佳实践,但如果出现循环依赖,你应该重新设计,而不是依赖 Spring 去修补。"

相关推荐
青山木2 小时前
Hot 100 --- 轮转数组
java·数据结构·算法
徐小夕2 小时前
Loop Engineering 深度解析与实战指南(全网最全)
前端·算法·github
Qt程序员2 小时前
掌握 Linux 内核调度:从原理到实现(进程篇)
java·开发语言
运筹vivo@2 小时前
Python ContextVar 底层机制与内存模型拆解
前端·数据库·python
code bean2 小时前
【LangChain】检索器完全指南:从向量检索到生产级 RAG 架构
java·开发语言·微服务
大白菜和MySQL2 小时前
java应用排查高线程
java·python
KobeSacre3 小时前
ReentrantLock源码
java
嵌入式协会20240723 小时前
(已解决)MinIO python 获取预签名出现forbidden、errornetwork等错误
java·开发语言·python
不才不才不不才3 小时前
Spring AI 实战:聊天、提示词、记忆三件套
java·人工智能·spring·ai