在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有预留给开发者哪些接口呢?这里统计以下,基本上每个阶段都有接口供我们个性化操作。
| 接口 | 介入时机 | 用途 | | --------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------- | | **`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实例化过程的步骤,记住先进行属性填充再初始化。
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 去修补。”
