
spring 容器在启动时即完成所有非懒加载单例 bean 的创建与初始化,而非等到 getbean() 调用时才实例化;因此 a 的构造函数最先执行,是因为 a 是 b 和 c 的共同依赖,spring 依据“依赖优先”原则,在初始化 b/c 前必须先确保 a 已就绪。
spring 容器在启动时即完成所有非懒加载单例 bean 的创建与初始化,而非等到 getbean() 调用时才实例化;因此 a 的构造函数最先执行,是因为 a 是 b 和 c 的共同依赖,spring 依据“依赖优先”原则,在初始化 b/c 前必须先确保 a 已就绪。
在 Spring IoC 容器中,Bean 的加载与初始化并非按 getBean() 的调用顺序执行,而是严格遵循依赖图(Dependency Graph)驱动的预初始化机制。以您提供的代码为例:
- A、B、C 均为 @Component 标注的单例 Bean,默认非懒加载;
- B 和 C 均通过 @Autowired setter 方法依赖 A(字段注入);
- 当 new AnnotationConfigApplicationContext(LegendConfig.class) 执行时,Spring 启动流程立即触发完整 Bean 生命周期管理。
? 核心执行逻辑(简化版)
- 扫描与注册:@ComponentScan 发现 A、B、C,将其定义注册到 BeanDefinitionRegistry;
- 依赖分析:Spring 构建依赖关系图 —— B → A、C → A,识别出 A 是被依赖方(injected bean),B/C 是依赖方(injecting beans);
- 按需前置初始化:根据“依赖优先”原则,容器必须先完全初始化 A(包括构造 → 属性填充 → 初始化回调),才能安全地注入到 B 和 C 中;
-
统一初始化调度:所有非懒加载单例 Bean 在 refresh() 阶段集中创建,顺序为:
- ✅ 先实例化并初始化 A(A 构造函数执行);
- ✅ 再实例化 B(B 构造函数执行),随后执行其 setA() 注入;
- ✅ 接着实例化 C(C 构造函数执行),再执行其 setA() 注入。
? 注意:getBean(B.class) 仅从已初始化完成的一级缓存中获取 B 实例,不触发新建流程 —— 这正是输出中 I am A constructor! 出现在最前的根本原因。
? 补充验证:构造注入 vs 字段/Setter 注入
若您将依赖改为构造注入,顺序逻辑更直观:
@Component
class B {
private final A a;
public B(A a) { // 构造时强制依赖 A
System.out.println("I am B constructor!");
this.a = a;
}
}
此时 Spring 会在 B 实例化前,确保 A 已完成整个生命周期(构造 + 初始化),否则无法调用 B 的构造函数 —— 这是 Spring 保证依赖可用性的底层契约。
⚠️ 关键注意事项
- @Order 对 Bean 加载顺序无效:它仅影响 BeanPostProcessor、Advice 或集合注入时的排序,不能控制 Bean 创建先后;
- @DependsOn 可显式声明无直接引用的逻辑依赖(如 @DependsOn("databaseInitializer")),但会绕过依赖图自动推导,需谨慎使用;
- 循环依赖仅对单例 + Setter/字段注入场景可解(借助三级缓存),构造注入或原型(Prototype)Bean 将直接抛出 BeanCurrentlyInCreationException;
- 若需严格控制初始化时机(如某 Bean 必须在所有其他 Bean 之前运行),推荐实现 ApplicationRunner 或 CommandLineRunner,而非强行干预 IoC 初始化顺序。
✅ 总结
Spring 的 Bean 初始化本质是声明式依赖驱动的编排过程,而非命令式调用链。理解 getBean() 是“取”,而非“造”,是掌握 Spring 生命周期的关键前提。只要牢记:
容器启动即完成非懒加载单例的全量初始化;依赖方永远晚于被依赖方;getBean() 仅是缓存访问操作。
这一原则贯穿 Spring Boot 自动配置、条件化加载及各类扩展点设计,是构建高可靠性 Spring 应用的基石。











