当前位置: 首页 > news >正文

在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 服务器)。就像开业流程固定,但“迎宾方式”可由各餐厅自己定。

  • 工厂模式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 去修补。”

http://www.rkmt.cn/news/1504904.html

相关文章:

  • 小米穿戴设备表盘设计:从零到一的视觉创作指南
  • htdemucs_6s音乐源分离:6秒完成六音轨精准分离的革命性工具
  • 沈阳高口碑黄金铂金回收白银回收实体老店排行 5 家靠谱门店电话地址全收录 - 诚金汇钻回收公司
  • COMSOL仿真揭秘:母线板温升下的电阻动态响应
  • 企业微信模板卡片消息实战:一个PHP代码示例搞定合同审批提醒(含版本兼容说明)
  • 从[特殊字符]到[特殊字符]:手把手教你用Python爬虫批量下载并分类所有Emoji图片(附代码)
  • OpenCore Simplify:重构黑苹果配置的技术哲学与工程实践
  • Windows下用FFmpeg sws_scale做RGB图像缩放+多图定位叠加的完整工程包
  • 2026深圳GEO优化公司推荐:昊客网络助力企业AI搜索时代抢占先机 - 猫头鹰AI推广
  • 用Python+Matplotlib可视化旋转曲面:从抛物线到双曲面的3D建模实战
  • 2026晋中贵金属回收黄金回收白银回收铂金回收店铺怎么挑?5 家不压价线下实体店完整测评清单 + 商家联络方式 - 信誉隆金银铂奢回收
  • Codesys ST语言实战:手把手教你封装一个可复用的循环队列功能块(附完整代码)
  • string类的模拟实现
  • MPC755嵌入式处理器电源与时序设计:硬件稳定性的关键解析
  • 2026攀枝花贵金属回收黄金回收白银回收铂金回收店铺怎么挑?5 家不压价线下实体店完整测评清单 + 商家联络方式 - 信誉隆金银铂奢回收
  • Python-Pandas从入门到实战:数据分析的“瑞士军刀”全指南
  • ExtractorSharp终极指南:零基础掌握游戏资源编辑的完整教程
  • S32K SPI实战:从时序图到代码实现的配置指南
  • 2026年华为云OpenClaw/Hermes Agent配置Token Plan安装步骤全公开
  • 声音的万花筒:在数字音乐迷宫中寻找属于自己的旋律
  • 如何利用SMUDebugTool深度调优AMD Ryzen处理器性能
  • 智谱与MiniMax港股股价分化,MiniMax调价风波下如何平衡C端与B端业务?
  • 2026年国产清洁度显微镜哪家好?苏州品恩VS进口品牌大测评 - 品牌推荐大师1
  • MC9S12NE64以太网硬件设计:从电气特性到PCB布局的实战指南
  • 武汉南华光电职业技术学校2026年招生简章(最新版) - 善良的阿良
  • 四川芥酸生产厂家实力排行及应用适配指南 - 奔跑123
  • 别再用递归硬扛了!用递推搞定‘踩方格’问题,信息学奥赛选手都在用的高效解法
  • 2026武汉珍珠棉厂家实力测评:定制包装领域优质厂商推荐 - 速递信息
  • 2026南阳本地人常去黄金回收门店前五整理 黄金回收百业回收铂金回收靠谱实体店联系方式汇总 - 中安检金银铂钻回收
  • 三分钟打造专业音乐播放器:foobar2000终极美化指南