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 服务器)。就像开业流程固定,但"迎宾方式"可由各餐厅自己定。 -
工厂模式 :
BeanFactory和ApplicationContext就是生产 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;
}
关键动作:一旦调用工厂拿到对象,立即:
-
将结果放入 二级缓存
-
从 三级缓存移除
这保证了:
-
同一个 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 去修补。"