本文 的 原文 地址
原始的内容,请参考 本文 的 原文 地址
尼恩说在前面
年底,大厂的hc越来越多, 反而 机会越混越多。 以前的金九银十, 现在变成 黄金年底。
在45岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格。
前两天一个 小伙伴面 滴滴,遇到的一个基础面试题:
SpringBoot类是怎么加载的?
小伙伴 背 了 双亲委派+Bean实例化 流程,自己很满意, 可是 面试官说才及格 , 没给通过。
小伙伴找尼恩复盘, 求助尼恩。
这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V176版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书
一:面试官问 “SpringBoot 类是怎么加载的?” 意图分析 + 回答套路

1、面试官 意图分析
这个问题属于SpringBoot 底层原理的高频核心题,面试官不是要你背流程,而是想通过你的回答,判断 3 个核心能力:
1. 基础功底:是否区分类加载器机制和Spring Bean 的加载流程(很多候选人会混淆这两个概念);
2. 原理深度:是否理解 SpringBoot 对 JVM 双亲委派模型的 “灵活改造”,以及为什么要这么改;
3. 实战能力:是否能结合实际开发问题(如依赖冲突、Bean 初始化异常、热部署原理)解释底层逻辑,而不是单纯背理论。
隐藏考察点
- 是否清楚
LaunchedURLClassLoader和RestartClassLoader的作用; - 是否能说清 “类加载” 和 “Bean 创建” 的关联与区别;
- 是否掌握控制 Bean 加载顺序的实战手段(解决项目中依赖注入失败、配置未生效等问题)。
2、标准化回答套路(总分总结构 + 分层拆解)
先总述 → 分两层拆解(JVM 类加载器层面 + Spring 容器 Bean 加载层面) → 结合实战价值收尾,逻辑上层层递进,既覆盖原理,又关联开发问题。
2.1. 总述:SpringBoot 类加载是 “两层机制” 的结合
SpringBoot 的类加载分为两个维度:
JVM 层面的类加载器机制:负责从文件系统 / Jar 包中加载 class 字节码到 JVM 内存,SpringBoot 通过自定义类加载器灵活调整加载规则;
**Spring 容器层面的 Bean 加载流程:**负责将已加载的 class 解析为 BeanDefinition,再按生命周期完成实例化、初始化,这部分继承 Spring 的核心逻辑,SpringBoot 做了自动配置增强。
两者的关系是:
- 先有类加载器把 class 加载进 JVM
- 才有 Spring 容器对 Bean 的管理。
2.2. 第一层:JVM 类加载器层面 —— 改造双亲委派,解决 FatJar 和热部署问题
这是 SpringBoot 和传统 Java 应用的核心区别,重点讲 “为什么改” 和 “怎么改”。
传统双亲委派的痛点:JVM 默认的类加载器(启动类加载器→扩展类加载器→应用类加载器)只能加载平级目录的 class,无法识别 FatJar 中 BOOT-INF/classes(业务类)和 BOOT-INF/lib(第三方依赖)的嵌套结构,同时无法实现热部署。
SpringBoot 的解决方案:自定义类加载器
场景 1:FatJar 运行 ——LaunchedURLClassLoader
- 打破点:对应用类(业务 + 第三方依赖)反向加载:先自己从
BOOT-INF路径加载,加载不到再委派父类加载器; - 核心价值:解决依赖隔离(如 Tomcat 自带的 guava-19 和应用依赖的 guava-31 共存,类加载器不同,互不干扰);
- 坚守双亲委派的部分:JDK 核心类(如 String、HashMap)仍由启动类加载器加载,保证安全。
场景 2:热部署 ——RestartClassLoader(DevTools)
- 设计:双类加载器模式 ——
BaseClassLoader加载不变的第三方依赖,RestartClassLoader加载频繁变动的业务类; - 打破点:业务类优先由
RestartClassLoader加载,修改代码后只需销毁重建这个类加载器,无需重启应用; - 核心价值:实现快速热部署,提升开发效率。
总结:SpringBoot 不是完全打破双亲委派,而是核心类守规矩,应用类搞灵活,平衡了安全和实用性。
2.3. 第二层:Spring 容器层面 ——Bean 的加载流程(SpringBoot 继承 + 增强)
这部分是 Spring 的核心逻辑,SpringBoot 的增强点在于自动配置,重点讲 “流程 + SpringBoot 的扩展”。
核心流程:从启动到 Bean 就绪
以SpringApplication.run()为入口,核心是refresh() 方法驱动的
分阶段初始化:
1. 环境准备:加载 application.yml、激活 Profile,构建 Environment;
2. BeanDefinition 定义注册:通过 BeanFactoryPostProcessor 解析 @Configuration、@ComponentScan,结合 SpringBoot 的自动配置(扫描 spring.factories/AutoConfiguration.imports,通过 @Conditional 条件筛选),生成 Bean 的 “蓝图”;
3. BFPP后置处理器注册:注册 BeanPostProcessor(如处理 @Autowired 的处理器、AOP 代理处理器);
4. Bean 实例化与初始化:按依赖拓扑排序,创建非懒加载单例 Bean,流程是:实例化→属性填充→初始化前置(触发 @PostConstruct)→初始化回调(afterPropertiesSet)→初始化后置(生成 AOP 代理)→放入单例池;
5. 容器就绪:发布 ContextRefreshedEvent 事件,应用启动完成。
SpringBoot 的增强点**:相比传统 Spring,多了 自动配置阶段**—— 通过条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean),根据依赖和配置动态决定是否创建 Bean,实现 “开箱即用”。
2.4. 实战延伸 加分项:如何控制加载顺序(解决实际开发问题)
面试官很关心你会不会用,这部分是加分项:
- 控制类加载顺序:通过调整依赖包的位置(
BOOT-INF/lib优先级)、自定义ClassLoader; - 控制 Bean 加载顺序:
- 用
@DependsOn强制指定 Bean 的依赖顺序; - 用
@AutoConfigureBefore/@AutoConfigureAfter控制自动配置类的加载顺序; - 用
@Order控制BeanPostProcessor等扩展点的执行顺序; - 实现
SmartInitializingSingleton在所有 Bean 初始化后执行逻辑。
- 用
2.5 总结:核心价值与设计思想
SpringBoot 的类加载机制,本质是在 JVM 类加载层面解决 “隔离与效率” 问题,在 Spring 容器层面解决 “自动与可控” 问题,既保证了 JDK 核心类的安全,又满足了企业级应用的灵活配置和快速开发需求。
3、避坑指南(常见错误回答)
1. 混淆 “类加载” 和 “Bean 加载”:把 LaunchedURLClassLoader 的作用说成是创建 Bean,这是底层概念错误;
2. 夸大 “打破双亲委派”:说 SpringBoot 完全打破了双亲委派,忽略了 JDK 核心类仍遵循双亲委派的事实;
3. 只背流程,不结合实战:全程讲 refresh() 方法的步骤,不提依赖隔离、热部署、Bean 初始化异常等实际问题;
4. 遗漏自动配置环节:讲 Bean 加载流程时,没提 SpringBoot 的自动配置,和传统 Spring 的流程混为一谈。
4、加分话术(面试亮点)
(1) “我在项目中遇到过 guava 版本冲突的问题,就是通过理解 LaunchedURLClassLoader 的依赖隔离机制,解决了 Tomcat 内置依赖和应用依赖的冲突;”
(2) “DevTools 的热部署本质是 RestartClassLoader 的销毁重建,所以在生产环境要关闭,避免类加载器频繁创建导致的内存问题;”
(3) “@PostConstruct 比 afterPropertiesSet 先执行,因为前者是 BeanPostProcessor 前置处理阶段触发,后者是初始化回调阶段,这个顺序搞反会导致依赖注入的 NPE。”
二:深入学习:SpringBoot 类的加载顺序 什么样的?
1、痛点拆解:为什么需要关注类加载顺序?
Spring 应用启动时,Bean 的创建和初始化顺序不明确,导致依赖注入失败、配置未生效、对象初始化异常等问题,本质上是开发者对 Spring 容器生命周期与 Bean 初始化时机的控制缺失。
在实际开发中,你是否遇到过以下问题?
-
使用 @Autowired 注入某个服务时,报错 “No qualifying bean of type”,但该 Bean 明明已定义;
-
Bean 在初始化过程中访问了尚未加载的配置或工具类,抛出空指针异常(NPE);
-
静态代码块或构造函数中读取环境变量,却发现值为空,因为配置类还未加载;
-
多个配置类之间存在隐式依赖,比如一个配置依赖另一个的 @Bean 方法结果,但后者尚未初始化,导致配置失效。
这些问题看似各异,实则根源相同:不了解 Spring 容器中 Bean 的加载与初始化顺序。
2:SpringBoot 并不是完全 打破 双亲委派
大家都知道 SpringBoot打包后是“fat-jar” , 一般都是知道这个知识点:SpringBoot 的类加载 打破双亲委派。
为啥要打破? 因为 SpringBoot打包后是“fat-jar”(一个包含所有依赖的大jar包),里面有业务代码 BOOT-INF/classes 和第三方依赖 BOOT-INF/lib( 比如guava、mybatis)。
但是, SpringBoot 的类加载 并不是完全 打破 双亲委派。
或者说: SpringBoot “10%的语义 打破了双亲委派”,又说“保留90%语义 没有打破”。
这里看上你去,是矛盾的。
为什么说 SpringBoot “打破了双亲委派”,又说“保留90%语义没有打破”?
这不是矛盾吗?
其实一点不矛盾——它只是“在需要灵活的地方调整,在需要安全的地方坚守”。
要搞懂这点,我们先把基础概念讲明白,再一步步拆解SpringBoot的操作。
3、先搞懂:传统双亲委派到底是啥?
传统双亲委派 就一条:“先找parent要现成的class ,parent 没有的情况下,我再自己加载class”。
具体逻辑:
-
当一个类加载器(比如应用类加载器)要加载某个类时,不会直接自己加载,而是先委派给“parent 加载器”(比如扩展类加载器);
-
parent 加载器也会继续委派给更上层的“爷爷”parent (启动类加载器,负责加载JDK核心类)。
只有当所有parent 加载器都“加载失败”(找不到这个类),子类加载器才会自己尝试加载。
传统双亲委派目的:保证安全和一致性。
比如JDK的核心类String,必须由最顶层的启动类加载器加载,避免有人写个“假String类”替换掉原生的,导致程序出问题。
4、SpringBoot的“小改动”:什么时候打破双亲委派?
SpringBoot并没有完全抛弃双亲委派。
在绝大多数情况下,Spring Boot 依然严格遵守 JVM 的双亲委派模型(Parent Delegation Model):
- 标准层次结构:Spring Boot 应用启动时,依然是由
Bootstrap ClassLoader加载核心库,Extension ClassLoader加载扩展库,App ClassLoader加载类路径下的类。 - 依赖关系:当你代码中引用
java.lang.String时,它永远是从最顶层的 Parent 加载,不会因为 Spring Boot 的介入而改变。这保证了 Java 核心运行环境的安全和统一。
为什么说“那 10% 打破了双亲委派”? 这主要体现在两个 场景中 :
场景A. 嵌套 Jar 的加载(Fat Jar 运行模式)
标准 JVM 无法直接从一个 Jar 包里的 Jar 包(Nested Jar)中加载类。
- 做法:Spring Boot 自定义了
LaunchedURLClassLoader。 - “打破”点:虽然它依然遵循向上委派,但它改变了查找类的逻辑(Find Resource)。它让 ClassLoader 具备了深入嵌套 Jar 内部寻找类的能力,这在原生双亲委派模型中是不存在的逻辑扩展。
场景B. DevTools 热部署
当你使用 spring-boot-devtools 时,这种“打破”最为明显。
做法:DevTools 热部署 场景 Spring Boot 会使用两个类加载器。
1. Base ClassLoader:加载不改变的库(如众多的第三方 Jar 包)。
2. Restart ClassLoader:加载你正在开发的、会频繁变动的类。
1. 场景A :用LaunchedURLClassLoader优先加载应用类
SpringBoot打包后是“fat-jar”(一个包含所有依赖的大jar包),里面有BOOT-INF/classes(你的业务代码)和BOOT-INF/lib(第三方依赖,比如guava、mybatis)。
它会创建一个专门的类加载器LaunchedURLClassLoader,启动时就把BOOT-INF/classes和BOOT-INF/lib的路径“记在心里”。加载这些路径下的类时,它会先自己尝试加载,加载不到再委派给父类加载器——这就打破了“先找爸爸”的规则。
举个实际例子:如果容器( 如Tomcat)本身依赖guava-19,而你的springboot maven里边 依赖guava-31。
此时Tomcat容器的guava-19由父类加载器加载,你的应用guava-31由 springboot 的LaunchedURLClassLoader加载。
guava-19、 guava-31两者虽然在同一个JVM里,但是不同的ClassLoader,所以, 互不干扰。
这就是“依赖隔离”,解决了传统双亲委派下的版本冲突问题。
LaunchedURLClassLoader实际的生命周期:
启动阶段:
1、JarLauncher 创建 LaunchedURLClassLoader,同时将fat-jar中BOOT-INF/classes、BOOT-INF/lib的路径纳入加载范围;
2、LaunchedURLClassLoader 优先加载应用主类、Spring Boot框架核心类及所有第三方依赖,完成基础环境初始化;
3、触发Spring Boot启动流程,协助完成Spring容器、内置Web容器(如Tomcat)的初始化工作。
运行阶段:
4、LaunchedURLClassLoader 持续承担核心类加载职责:
- 加载首次访问的业务类(如Controller、Service、Mapper等);
- 加载运行时动态生成的类(如Spring AOP代理类、MyBatis Mapper代理类);
- 处理应用运行过程中所有新增的类加载请求,保障业务正常执行。
关闭阶段:
5、应用接收关闭指令(如执行shutdown命令)后,停止类加载工作;
6、随着JVM进程终止或相关引用释放,LaunchedURLClassLoader被垃圾回收(GC),生命周期结束。
2. 场景B. DevTools 热部署:用RestartClassLoader支持运行阶段 热部署
如果用了SpringBoot的DevTools(热部署功能),它会再创建一个RestartClassLoader。
这个加载器也重写了加载规则:遇到你配置的“需要热部署的类”(比如业务代码),会先自己加载;遇到不需要热部署的类(比如JDK核心类),还是走双亲委派。
目的:
你改一行业务代码后,RestartClassLoader能直接重新加载这个类,不用重启整个应用,而且不会污染主加载器(避免影响其他稳定的类)——这就是“改一行代码立即生效”的原理。
如果用了SpringBoot的DevTools(热部署功能),会由LaunchedURLClassLoader作为父加载器,创建RestartClassLoader。
RestartClassLoader 加载器也重写了加载规则:遇到你配置的“需要热部署的类”(比如业务代码),会先自己加载;遇到不需要热部署的类(比如JDK核心类),还是走双亲委派。
RestartClassLoader实际的生命周期:
第一阶段,初始化阶段:
1、应用启动时,DevTools检测到热部署配置后,由LaunchedURLClassLoader委派创建RestartClassLoader;
2、 读取restart.include/restart.exclude配置,明确需要接管加载的类范围(默认包含业务代码、自定义依赖等);
3、 从LaunchedURLClassLoader获取已加载的基础类信息,仅接管配置范围内的类加载权限。
第2阶段,运行及热部署阶段:
4、 正常运行时,RestartClassLoader负责加载、管理配置范围内的业务类及动态生成类;
5、 检测到业务代码变更(如Java文件修改保存)后:
- 销毁当前RestartClassLoader实例,释放对变更类的引用;
- 重新创建新的RestartClassLoader实例;
- 由新实例重新加载变更后的业务类及关联依赖类,完成热部署;
6、 非配置范围内的类加载请求(如JDK核心类、Spring框架核心类),仍委派给父加载器LaunchedURLClassLoader处理。
第3阶段,关闭阶段:
7、 应用正常关闭时,RestartClassLoader随LaunchedURLClassLoader一同被GC回收;
8、 热部署过程中,旧的RestartClassLoader实例因失去引用,会被GC自动回收,避免内存泄漏。
目的:
你改一行业务代码后,RestartClassLoader能直接重新加载这个类,不用重启整个应用,而且不会污染主加载器(避免影响其他稳定的类)——这就是“改一行代码立即生效”的原理。
5、SpringBoot的“坚守”:90%场景仍遵循双亲委派
-
90%场景 ,SpringBoot “坚守” 遵循双亲委派
-
10%场景 ,SpringBoot “打破” 双亲委派
SpringBoot “打破” 双亲委派10%场景 ,只针对“ 业务代码、SpringBoot核心框架类、第三方依赖”(占比不到10%)。
对于以下核心类,SpringBoot仍严格遵守“先找爸爸”的规则,这就是“ 90% ” 意思:
1、JDK核心类:
比如String、Object、HashMap这些JDK自带的类,必须由父类加载器最终委派给启动类加载器加载。
如果让LaunchedURLClassLoader优先加载,可能出现“假String类”,违反Java安全规则。
2、容器自身类:
比如SpringBoot内置Tomcat的核心类(org.apache.catalina包下的类),由Tomcat自己的类加载器加载,
LaunchedURLClassLoader不会插手。如果乱加载,可能导致Tomcat运行异常。
3、系统级依赖类:
比如JAXB(Java的XML解析框架,属于JDK扩展功能),由扩展类加载器加载,保证多个应用共享这个框架时的一致性。
6、一张表看懂:SpringBoot vs 传统双亲委派
| 对比维度 | 传统双亲委派 | SpringBoot类加载 |
|---|---|---|
| 核心规则 | 先委派父类加载,父类失败才自己加载 | 应用相关类(业务、第三方依赖)先自己加载,再找父类;核心类(JDK、容器)仍先找父类 |
| 解决的问题 | 保证类加载安全、避免核心类被篡改 | 解决依赖版本冲突、支持热部署,同时保证核心类安全 |
| 适用场景 | 通用Java程序(如普通Jar包) | SpringBoot应用(fat-jar部署、需要热部署) |
7、核心总结(一句话看懂)
SpringBoot的类加载逻辑是:“核心类守规矩(遵循双亲委派),应用类搞灵活(优先自己加载)”。
既解决了传统双亲委派的版本冲突、热部署难问题,又不违反Java安全规则,这就是“打破又保留”的本质。
三:深入学习:Spring Boot 的 Bean 创建过程 是什么 ?
简单来说,Spring Boot 的 Bean 创建过程, 本质上就是 Spring 的过程.
因为 Spring Boot 是建立在 Spring 框架之上的“增强版”。
如果把 Spring 比作一台需要你手动组装、调试每一颗螺丝的“手动挡汽车”,那么 Spring Boot 就是一台带有“自动驾驶和预设配置”的“自动挡汽车”。
它们的核心区别不在于“怎么创建 Bean”(即生命周期链路),而在于**“如何发现并决定创建哪些 Bean”**。
1. 核心链路对比:底层逻辑一致
无论是 Spring 还是 Spring Boot,一旦确定要创建一个 Bean,它们都会走 那套bean的创建流程:
实例化 → 属性填充 → 初始化前置 → 初始化回调 → 初始化后置 → 放入单例池。
2. 主要区别:Bean 的“发现”与“注册”
这是两者差异最大的地方,主要体现在以下三个维度:
| 维度 | Spring (经典版) | Spring Boot (现代版) |
|---|---|---|
| 配置来源 | 显式配置:XML 文件或手写大量的 @Configuration 类。 | 自动配置:基于类路径(Classpath)下的 Jar 包自动推断。 |
| 容器启动 | 需要手动创建 ApplicationContext(如 ClassPathXmlApplicationContext)。 | 通过 SpringApplication.run() 一键启动,自动关联上下文。 |
| 条件装配 | 较少使用,通常手动控制 Bean 是否加载。 | 大量使用 @Conditional 族注解(如 @ConditionalOnClass),根据环境动态决定是否创建 Bean。 |
3. Spring Boot 特有的“自动配置”阶段
在 Spring 的标准流程中,Spring Boot 在 “解析配置类(流程图中的 B 步骤)” 时插入了黑科技:
1、加载 spring.factories / AutoConfiguration.imports:
Spring Boot 会扫描所有 Jar 包下的配置文件,获取成百上千个“候选”配置类。
2、条件筛选(Condition Evaluation):
这是 Spring Boot 的灵魂。
它会检查:
- “你的类路径里有 Redis 的驱动吗?”(@ConditionalOnClass)
- “你在 application.yml 里配置了数据库连接吗?”(@ConditionalOnProperty)
- “你自己已经手动定义过这个 Bean 了吗?”(@ConditionalOnMissingBean)
3、动态注册:
只有满足条件的 Bean 才会转化为 BeanDefinition 注册进容器,进入后续的创建流程。
4. 总结:多出来的“前戏”
- Spring:你告诉它干什么,它就干什么(你定义一个 Bean,它创一个 Bean)。
- Spring Boot:它先看你引入了什么依赖,然后“自作聪明”地帮你准备好一大堆 BeanDefinition。只有当你明确不需要(或没提供条件)时,它才不创建。
这就好比:
- Spring 是点菜:你不点,厨房就不动。
- Spring Boot 是自助餐:厨师根据今天的客人类型(依赖),预先摆好了大部分你可能需要的菜(Bean),你只需要直接吃或者根据口味微调即可。
四、深入学习: Spring 如何组织加载流程?
在 Spring 应用启动过程中,成百上千的 Bean 和配置交织在一起,若没有清晰的加载顺序控制机制,极易导致依赖未就绪(如配置未读取、Bean 未初始化)、组件执行错序(如后置处理器晚于目标 Bean 执行)等问题,最终引发 NullPointerException 或注入失败等运行时异常。
Spring 通过“分阶段初始化 + 特殊组件优先处理”的策略,确保容器先完成环境与元数据准备,再逐步构建 Bean 实例。整个流程遵循“先配置后实例、先基础设施后业务逻辑”的原则,实现可预测且稳定的加载顺序。
1. 启动入口:SpringApplication.run()
所有 Spring Boot 应用都从 SpringApplication.run() 方法开始。
这一行代码看似简单,实则触发了整个应用上下文的初始化流程。
它会创建并引导 ApplicationContext 完成从环境准备到容器刷新的全过程,是整个加载链条的起点。
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
该方法内部封装了多个关键步骤,确保各阶段按序执行,为后续的 Bean 加载奠定基础。
2. 核心阶段划分
Spring 将容器初始化过程划分为多个明确阶段,每个阶段职责单一、顺序固定,从而保证加载行为可控、可调试。
| 阶段 | 主要任务 |
|---|---|
| ① 环境准备(Environment) | 加载 application.properties/yml,解析激活 Profile,构建 Environment 对象 |
| ② BeanDefinition 扫描与注册 | 处理 @ComponentScan、@Configuration 等注解,将类信息解析为 BeanDefinition 并注册到容器 |
| ③ BeanFactory 初始化 | 注册 BeanFactoryPostProcessor 和 BeanPostProcessor,为后续扩展留出钩子 |
| ④ Bean 创建与依赖注入 | 实例化非懒加载的单例 Bean,完成字段和方法级别的 @Autowired 注入 |
| ⑤ 容器刷新完成 | 发布 ContextRefreshedEvent 事件,通知系统已就绪 |
关键设计思想:
先注册元信息,再创建实例;先处理配置,再初始化业务组件。
这种“两阶段模型”避免了早期访问未定义资源的问题。
3. 影响加载顺序的核心因素
虽然 Spring 有默认的加载流程,但在实际开发中,我们常需干预或理解加载顺序以解决依赖冲突。
以下是三大主要影响因素:
(1)注解驱动的优先级控制
Spring 提供一系列注解来显式或隐式地调整加载和执行顺序:
| 注解 | 作用 |
|---|---|
| @DependsOn(“beanName”) | 强制指定某个 Bean 必须在当前 Bean 之前完成初始化 |
| @Primary | 当存在多个候选 Bean 时,优先选择该 Bean 进行自动注入 |
| @Lazy | 延迟初始化 Bean,避免在启动阶段提前加载,常用于非核心组件 |
| @Order / 实现 Ordered 接口 | 控制 BeanPostProcessor、AOP 切面、事件监听器等的执行顺序,数值越小优先级越高 |
️ 注意:@Order 不影响普通 Bean 的创建顺序,仅对特定扩展点生效。
(2)配置类之间的导入关系
配置类不仅是 Bean 的来源,也决定了加载时机:
- @Import(Config.class):强制将目标配置类提前加载,常用于模块化配置引入。
- @Configuration 类本身会被当作代理 Bean 处理,其内部 @Bean 方法会在适当阶段被调用生成实例。
- @PropertySource 必须在早期注册,以便在后续配置中使用属性占位符(如 ${db.url}),否则可能读取失败。
(3)特殊 Bean 类型的自动优先处理
Spring 框架会自动识别并优先处理以下基础设施类 Bean,因为它们用于“改造”容器自身行为:
| 类型 | 说明 |
|---|---|
| BeanFactoryPostProcessor (如 ConfigurationClassPostProcessor) | 在所有 BeanDefinition 注册完成后、实例化前修改配置元数据 |
| BeanPostProcessor | 为所有 Bean 提供初始化前后增强能力(如 AOP 代理) |
| ApplicationListener | 监听容器事件(如 ContextRefreshedEvent) |
| EnvironmentPostProcessor (Spring Boot 特有) | 在环境准备阶段介入,动态添加属性源或修改环境变量 |
这些类型无需手动排序,Spring 会自动将其注册并优先执行,确保容器功能完整后再进入业务 Bean 构建阶段。
总结回顾:
- 核心痛点:Bean 与配置错序加载导致依赖缺失、初始化失败。
- 核心方案:采用“分阶段 + 优先级”机制,保障关键组件先行,业务组件后置。
整个流程干净利落:从 run() 出发 → 环境就绪 → 元数据注册 → 容器准备 → Bean 实例化 → 容器就绪,层层递进,环环相扣。掌握这一脉络,即可从容应对复杂场景下的加载控制问题。
五、源码落地:从启动到 Bean 初始化的关键路径
由于平台篇幅限制,这里略5000字 …
…由于平台篇幅限制, 剩下的内容(5000字+),请参参见原文地址
原始的内容,请参考 本文 的 原文 地址
六、深入学习: Bean 初始化 精准顺序
由于平台篇幅限制,这里略5000字 …
…由于平台篇幅限制, 剩下的内容(5000字+),请参参见原文地址
原始的内容,请参考 本文 的 原文 地址
八、深入学习:调试与观察 加载顺序 的 工具
由于平台篇幅限制,这里略5000字 …
…由于平台篇幅限制, 剩下的内容(5000字+),请参参见原文地址
原始的内容,请参考 本文 的 原文 地址
九、深入学习: 如何控制类加载顺序?
由于平台篇幅限制,这里略5000字 …
…由于平台篇幅限制, 剩下的内容(5000字+),请参参见原文地址

1074

被折叠的 条评论
为什么被折叠?



