Spring IoC 容器原理深度解析
面试官:说说你对 Spring IoC 的理解?
你:IoC(控制反转)是一种设计思想,将对象的创建和依赖管理从对象自身转移到容器。通过依赖注入(DI)实现解耦,让对象被动接受依赖而非主动创建。
面试官:那 Spring Bean 的生命周期是怎样的?
这个问题很多人只能说出”实例化、属性注入、初始化、销毁”,但面试官真正想考察的是你对扩展点时机和AOP代理创建时机的理解。
链式追问一:Bean 生命周期完整流程
Section titled “链式追问一:Bean 生命周期完整流程”Q1:Spring Bean 的完整生命周期有哪些阶段?必考
Section titled “Q1:Spring Bean 的完整生命周期有哪些阶段?”回答要点:
┌─────────────────────────────────────────────────────────────┐│ 1. 实例化(Instantiation) │├─────────────────────────────────────────────────────────────┤│ • InstantiationAwareBPP.postProcessBeforeInstantiation() ││ → 可返回代理对象,短路后续流程 ││ • 通过构造方法创建 Bean 实例(属性未注入) ││ • InstantiationAwareBPP.postProcessAfterInstantiation() │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 2. 属性填充(Population) │├─────────────────────────────────────────────────────────────┤│ • InstantiationAwareBPP.postProcessProperties() ││ → @Autowired、@Value 在此注入 ││ • 填充普通属性(setter 或字段) │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 3. 初始化(Initialization) │├─────────────────────────────────────────────────────────────┤│ • Aware 接口回调(setBeanName、setApplicationContext) ││ • BeanPostProcessor.postProcessBeforeInitialization() ││ → @PostConstruct 在此执行 ││ • InitializingBean.afterPropertiesSet() ││ • 自定义 init-method ││ • BeanPostProcessor.postProcessAfterInitialization() ││ → ★ AOP 代理在此创建! │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 4. 使用中(In Use) │├─────────────────────────────────────────────────────────────┤│ Bean 存活于容器,响应依赖注入请求 │└─────────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────────┐│ 5. 销毁(Destruction) │├─────────────────────────────────────────────────────────────┤│ • @PreDestroy 注解方法 ││ • DisposableBean.destroy() ││ • 自定义 destroy-method │└─────────────────────────────────────────────────────────────┘代码示例:
@Componentpublic class LifeCycleBean implements BeanNameAware, InitializingBean, DisposableBean {
private String beanName;
// 1. 构造方法(实例化) public LifeCycleBean() { System.out.println("1. 构造方法执行"); }
// 2. Aware 接口回调 @Override public void setBeanName(String name) { this.beanName = name; System.out.println("2. BeanNameAware: " + name); }
// 3. @PostConstruct(初始化前) @PostConstruct public void postConstruct() { System.out.println("3. @PostConstruct 执行"); }
// 4. InitializingBean 接口 @Override public void afterPropertiesSet() { System.out.println("4. afterPropertiesSet 执行"); }
// 5. 自定义 init-method public void customInit() { System.out.println("5. 自定义 init-method"); }
// 6. @PreDestroy(销毁) @PreDestroy public void preDestroy() { System.out.println("6. @PreDestroy 执行"); }
// 7. DisposableBean 接口 @Override public void destroy() { System.out.println("7. DisposableBean.destroy()"); }}本质一句话:Bean 生命周期是实例化 → 属性注入 → Aware回调 → BeanPostProcessor前置 → 初始化 → BeanPostProcessor后置(AOP代理)→ 使用 → 销毁。
Q2:为什么 AOP 代理在初始化之后创建,而不是实例化之后?高频
Section titled “Q2:为什么 AOP 代理在初始化之后创建,而不是实例化之后?”关键细节:
-
属性注入可能影响代理逻辑
- 如果代理需要访问 Bean 的属性(如事务管理需要数据源),必须等属性注入完成
- 实例化后属性为 null,无法创建功能完整的代理
-
@PostConstruct 可能修改 Bean 状态
- 初始化方法可能修改 Bean 的字段或配置
- 代理需要拦截的是最终状态的方法调用
-
BeanPostProcessor 设计理念
postProcessBeforeInitialization:初始化前的预处理postProcessAfterInitialization:初始化后的后处理,此时 Bean 完全准备好
Q3:如果 Bean 的构造方法依赖另一个 Bean,Spring 如何处理?实战
Section titled “Q3:如果 Bean 的构造方法依赖另一个 Bean,Spring 如何处理?”场景示例:
@Servicepublic class OrderService { private final UserService userService;
// 构造器注入(推荐) @Autowired // 只有一个构造器时可省略 public OrderService(UserService userService) { this.userService = userService; }}Spring 处理流程:
创建 OrderService: 1. 选择构造方法(优先 @Autowired 标注的) 2. 解析构造参数 → 发现需要 UserService 3. 检查 UserService 是否已创建 • 已创建 → 直接从一级缓存获取 • 未创建 → 递归创建 UserService 4. 调用构造方法完成 OrderService 实例化构造器循环依赖问题:
@Servicepublic class A { private final B b; public A(B b) { this.b = b; }}
@Servicepublic class B { private final A a; public B(A a) { this.a = a; }}
// 启动时报错:// BeanCurrentlyInCreationException: Error creating bean with name 'a'解决方案:
// 方案1:改用 Setter 注入(延迟依赖)@Servicepublic class A { private B b; @Autowired public void setB(B b) { this.b = b; }}
// 方案2:使用 @Lazy 延迟加载@Servicepublic class A { private final B b; public A(@Lazy B b) { // 注入代理,首次使用时才创建真实对象 this.b = b; }}链式追问二:三级缓存解决循环依赖
Section titled “链式追问二:三级缓存解决循环依赖”Q1:Spring 如何解决循环依赖?必考
Section titled “Q1:Spring 如何解决循环依赖?”回答要点:
通过三级缓存:singletonObjects(一级)、earlySingletonObjects(二级)、singletonFactories(三级)。
三级缓存结构:
singletonObjects(一级缓存,ConcurrentHashMap): key: beanName value: 完整的单例 Bean(已实例化、属性填充、初始化完成)
earlySingletonObjects(二级缓存,HashMap): key: beanName value: 早期暴露的 Bean(已实例化,属性未填充) 存放从三级缓存中获取并执行工厂方法后的对象
singletonFactories(三级缓存,HashMap): key: beanName value: ObjectFactory 工厂方法 存放 Lambda: () -> getEarlyBeanReference(beanName, mbd, bean)循环依赖解决流程(A 依赖 B,B 依赖 A):
创建 Bean A: 1. 标记 A 正在创建(singletonsCurrentlyInCreation.add("a")) 2. 实例化 A(构造方法完成) 3. 将 A 的工厂方法放入三级缓存 singletonFactories.put("a", () -> getEarlyBeanReference("a", mbd, a)) 4. 开始填充属性,发现依赖 B → 递归创建 B
创建 Bean B: 1. 标记 B 正在创建 2. 实例化 B 3. 将 B 的工厂方法放入三级缓存 4. 开始填充属性,发现依赖 A 5. 查找 A: 一级缓存 singletonObjects → 无(A 未完成初始化) 二级缓存 earlySingletonObjects → 无 三级缓存 singletonFactories → 有! 6. 执行工厂方法,得到 A 的早期引用 • 如 A 需要 AOP 代理,工厂方法返回代理对象 • 如无 AOP,返回原始对象 7. 将 A 的早期引用放入二级缓存,删除三级缓存 earlySingletonObjects.put("a", earlyA) singletonFactories.remove("a") 8. B 持有 A 的早期引用,完成属性填充 9. B 初始化完成,放入一级缓存 singletonObjects.put("b", b)
回到创建 A: 10. A 获得 B(从一级缓存),完成属性填充 11. A 初始化完成 12. 检查二级缓存中是否有 A 的早期引用 • 有 → 说明发生了循环依赖,使用二级缓存中的对象 • 无 → 直接使用当前对象 13. 将最终版本的 A 放入一级缓存,删除二级缓存本质一句话:三级缓存通过提前暴露”工厂方法”延迟 AOP 代理创建,在循环依赖真正需要早期引用时才创建代理,确保整个生命周期只创建一个代理实例。
Q2:为什么需要三级缓存,两级不够吗?高频
Section titled “Q2:为什么需要三级缓存,两级不够吗?”关键理解:
如果没有 AOP,两级缓存就足够:
singletonObjects(一级):完整 Bean earlySingletonObjects(二级):早期 Bean
实例化后直接放入二级缓存 循环依赖时从二级缓存获取
但是有了 AOP:
问题1:代理对象何时创建? 正常情况:初始化后(postProcessAfterInitialization) 循环依赖时:需要提前创建(否则注入的是原始对象)
问题2:如果直接在实例化后创建代理放入二级缓存? 代码侵入:需要提前判断是否有循环依赖 重复创建:正常流程还会再创建一次代理
三级缓存的巧妙之处: 实例化后放入工厂方法(Lambda),延迟代理创建 只有在循环依赖真正需要时,才调用工厂方法创建代理 既延迟了代理创建,又保证了唯一性对比表格:
| 缓存级别 | 作用 | 何时放入 | 何时移除 |
|---|---|---|---|
| 一级缓存 | 存储完整 Bean | 初始化完成 | 容器关闭 |
| 二级缓存 | 存储早期引用 | 三级缓存工厂方法执行后 | Bean 进入一级缓存 |
| 三级缓存 | 存储工厂方法 | 实例化后 | 工厂方法执行后(移至二级) |
Q3:哪些循环依赖无法解决?实战
Section titled “Q3:哪些循环依赖无法解决?”无法解决的场景:
1. 构造器循环依赖(最常见)
@Service public class A { private final B b; public A(B b) { this.b = b; } // 构造方法需要 B }
@Service public class B { private final A a; public B(A a) { this.a = a; } // 构造方法需要 A }
错误原因: 创建 A 时需要先实例化 B 创建 B 时需要先实例化 A 实例化阶段就陷入死锁,三级缓存还未介入
→ Spring 抛出 BeanCurrentlyInCreationException
2. prototype 作用域的循环依赖
@Scope("prototype") @Service public class A { @Autowired private B b; }
@Scope("prototype") @Service public class B { @Autowired private A a; }
错误原因: prototype Bean 每次都创建新实例,不使用缓存 Spring 无法管理其完整生命周期
→ Spring 抛出 BeanCurrentlyInCreationException
3. @Async + 循环依赖(Spring Boot 2.6+)
@Service public class A { @Autowired private B b; @Async // 异步方法代理 public void asyncMethod() {} }
@Service public class B { @Autowired private A a; }
错误原因: @Async 的代理创建时机与 AOP 不同 可能导致注入的不是代理对象
→ Spring Boot 2.6+ 默认禁止循环依赖,启动报错解决方案:
// 1. 构造器循环依赖:使用 @Lazy@Servicepublic class A { private final B b; public A(@Lazy B b) { // 注入代理对象 this.b = b; }}
// 2. 重构代码,消除循环依赖(最佳方案)@Servicepublic class A { @Autowired private B b;}
@Servicepublic class B { // 移除对 A 的依赖,通过事件或回调解耦}链式追问三:依赖注入最佳实践
Section titled “链式追问三:依赖注入最佳实践”Q1:构造器注入、Setter 注入、字段注入,哪个更好?必考
Section titled “Q1:构造器注入、Setter 注入、字段注入,哪个更好?”对比表格:
| 注入方式 | 依赖明确性 | 不可变性 | 测试友好性 | 循环依赖检测 | 推荐度 |
|---|---|---|---|---|---|
| 构造器注入 | ★★★★★ | final 字段 | 直接 new | 启动时报错 | ★★★★★ |
| Setter 注入 | ★★★☆☆ | 可变 | 需要 setter | 运行时暴露 | ★★★☆☆ |
| 字段注入 | ★☆☆☆☆ | 不可 final | 需要反射 | 运行时暴露 | ★☆☆☆☆ |
代码示例:
// 1. 构造器注入(Spring 官方推荐)@Servicepublic class OrderService { private final UserService userService; // final,不可变 private final OrderMapper orderMapper;
@Autowired // 只有一个构造器时可省略 public OrderService(UserService userService, OrderMapper orderMapper) { this.userService = userService; this.orderMapper = orderMapper; }}
// 2. Setter 注入(可选依赖)@Servicepublic class NotificationService { private MessageService messageService; // 非必需依赖
@Autowired(required = false) // 容器中没有也不报错 public void setMessageService(MessageService messageService) { this.messageService = messageService; }}
// 3. 字段注入(不推荐)@Servicepublic class OrderService { @Autowired private UserService userService; // 无法 final,测试困难
public void createOrder() { userService.createUser(); // 空指针风险 }}测试对比:
// 构造器注入:测试友好@Testpublic void testCreateOrder() { UserService mockUserService = Mockito.mock(UserService.class); OrderMapper mockOrderMapper = Mockito.mock(OrderMapper.class);
// 直接 new,无需 Spring 容器和反射 OrderService orderService = new OrderService(mockUserService, mockOrderMapper); orderService.createOrder();
verify(mockUserService).createUser();}
// 字段注入:测试困难@Testpublic void testCreateOrder() { OrderService orderService = new OrderService();
// 需要反射注入依赖 ReflectionTestUtils.setField(orderService, "userService", mockUserService);
orderService.createOrder();}Q2:@Autowired、@Resource、@Inject 有什么区别?中频
Section titled “Q2:@Autowired、@Resource、@Inject 有什么区别?”对比表格:
| 注解 | 来源 | 注入策略 | 指定 Bean | 适用场景 |
|---|---|---|---|---|
@Autowired | Spring | 先 byType,多个则 byName | @Qualifier("beanName") | Spring 项目首选 |
@Resource | JSR-250(Java 标准) | 先 byName,无则 byType | @Resource(name="beanName") | 不依赖 Spring |
@Inject | JSR-330(Java 标准) | 先 byType,同 @Autowired | @Named("beanName") | 需额外依赖 |
注入流程对比:
@Autowired 注入流程: 1. 按类型查找(byType) → 找到 0 个:报错(required=true 时) → 找到 1 个:注入 → 找到多个: 2. 按 @Qualifier 指定的名称查找 3. 按字段名/参数名查找 4. 按 @Primary 标注的 Bean 5. 仍无法确定:报错 NoUniqueBeanDefinitionException
@Resource 注入流程: 1. 按 name 属性指定的名称查找(byName) 2. 未指定 name,按字段名查找 3. 未找到,按类型查找(byType)代码示例:
// @Autowired + @Qualifier@Servicepublic class OrderService { @Autowired @Qualifier("primaryUserService") // 指定 Bean 名称 private UserService userService;}
// 多个实现,使用 @Primary 标注默认@Service@Primary // 优先注入public class PrimaryUserService implements UserService {}
@Servicepublic class SecondaryUserService implements UserService {}
// @Resource 直接指定名称@Servicepublic class OrderService { @Resource(name = "primaryUserService") private UserService userService;}链式追问四:BeanFactory vs ApplicationContext
Section titled “链式追问四:BeanFactory vs ApplicationContext”Q1:BeanFactory 和 ApplicationContext 有什么区别?高频
Section titled “Q1:BeanFactory 和 ApplicationContext 有什么区别?”对比表格:
| 对比项 | BeanFactory | ApplicationContext |
|---|---|---|
| 定位 | IoC 容器基础接口 | BeanFactory 子接口,功能完整 |
| Bean 实例化 | 懒加载(首次 getBean) | 预实例化(启动时创建所有单例) |
| 国际化 | 不支持 | 支持 MessageSource |
| 事件发布 | 不支持 | 支持 ApplicationEvent |
| 资源访问 | 不支持 | 支持 ResourceLoader |
| AOP 集成 | 需手动配置 | 自动集成 |
| 实际使用 | 很少直接用 | Spring 应用实际容器 |
启动性能对比:
BeanFactory(懒加载): 启动耗时:快(不实例化 Bean) 首次请求:慢(需要实例化依赖链) 适用场景:资源受限环境、移动端
ApplicationContext(预实例化): 启动耗时:慢(实例化所有单例 Bean) 首次请求:快(Bean 已就绪) 适用场景:服务端应用(绝大多数场景)
性能数据: 100 个单例 Bean 的启动时间: BeanFactory: ~50ms ApplicationContext: ~500ms 首次请求响应时间: BeanFactory: ~50ms(实例化耗时) ApplicationContext: ~1ms(直接返回)代码示例:
// BeanFactory 使用(很少用)DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);reader.loadBeanDefinitions("applicationContext.xml");
UserService userService = (UserService) beanFactory.getBean("userService"); // 懒加载
// ApplicationContext 使用(推荐)ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = context.getBean(UserService.class); // 已实例化
// Spring Boot 默认:AnnotationConfigServletWebServerApplicationContext高频面试题速查
Section titled “高频面试题速查”Q:Spring Bean 的生命周期?
实例化(构造方法)→ 属性填充(@Autowired)→ Aware 接口回调 → BeanPostProcessor 前置(@PostConstruct)→ 初始化(afterPropertiesSet + init-method)→ BeanPostProcessor 后置(AOP 代理创建)→ 使用 → 销毁(@PreDestroy + destroy-method)。
Q:Spring 如何解决循环依赖?
通过三级缓存:singletonObjects(完整 Bean)、earlySingletonObjects(早期引用)、singletonFactories(工厂方法)。实例化后放入工厂方法到三级缓存;循环依赖时从三级缓存获取早期引用(触发工厂方法,如有 AOP 则创建代理)放入二级缓存;最终初始化完成后放入一级缓存。
Q:为什么需要三级缓存而不是两级?
三级缓存通过 ObjectFactory 延迟 AOP 代理创建。如果没有 AOP,二级缓存足够;但有 AOP 时,需要在循环依赖真正需要早期引用时才创建代理,确保整个生命周期只创建一个代理实例。如果直接在实例化后创建代理,会侵入正常流程。
Q:构造器注入和字段注入哪个好?
构造器注入:依赖明确、字段可 final、测试友好、循环依赖启动时报错。字段注入:代码简洁但依赖隐式、无法 final、测试需要反射、循环依赖运行时才暴露。Spring 官方推荐构造器注入。
Q:哪些循环依赖无法解决?
构造器循环依赖(实例化阶段死锁)、prototype 作用域循环依赖(不使用缓存)、@Async + 循环依赖(代理创建时机特殊)。解决方案:使用 @Lazy 延迟加载或重构代码消除循环依赖。