Skip to content

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 │
└─────────────────────────────────────────────────────────────┘

代码示例

@Component
public 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 代理在初始化之后创建,而不是实例化之后?”

关键细节

  1. 属性注入可能影响代理逻辑

    • 如果代理需要访问 Bean 的属性(如事务管理需要数据源),必须等属性注入完成
    • 实例化后属性为 null,无法创建功能完整的代理
  2. @PostConstruct 可能修改 Bean 状态

    • 初始化方法可能修改 Bean 的字段或配置
    • 代理需要拦截的是最终状态的方法调用
  3. BeanPostProcessor 设计理念

    • postProcessBeforeInitialization:初始化前的预处理
    • postProcessAfterInitialization:初始化后的后处理,此时 Bean 完全准备好

Q3:如果 Bean 的构造方法依赖另一个 Bean,Spring 如何处理?实战

Section titled “Q3:如果 Bean 的构造方法依赖另一个 Bean,Spring 如何处理?”

场景示例

@Service
public 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 实例化

构造器循环依赖问题

@Service
public class A {
private final B b;
public A(B b) { this.b = b; }
}
@Service
public class B {
private final A a;
public B(A a) { this.a = a; }
}
// 启动时报错:
// BeanCurrentlyInCreationException: Error creating bean with name 'a'

解决方案

// 方案1:改用 Setter 注入(延迟依赖)
@Service
public class A {
private B b;
@Autowired
public void setB(B b) { this.b = b; }
}
// 方案2:使用 @Lazy 延迟加载
@Service
public 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
@Service
public class A {
private final B b;
public A(@Lazy B b) { // 注入代理对象
this.b = b;
}
}
// 2. 重构代码,消除循环依赖(最佳方案)
@Service
public class A {
@Autowired private B b;
}
@Service
public class B {
// 移除对 A 的依赖,通过事件或回调解耦
}

链式追问三:依赖注入最佳实践

Section titled “链式追问三:依赖注入最佳实践”

Q1:构造器注入、Setter 注入、字段注入,哪个更好?必考

Section titled “Q1:构造器注入、Setter 注入、字段注入,哪个更好?”

对比表格

注入方式依赖明确性不可变性测试友好性循环依赖检测推荐度
构造器注入★★★★★final 字段直接 new启动时报错★★★★★
Setter 注入★★★☆☆可变需要 setter运行时暴露★★★☆☆
字段注入★☆☆☆☆不可 final需要反射运行时暴露★☆☆☆☆

代码示例

// 1. 构造器注入(Spring 官方推荐)
@Service
public 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 注入(可选依赖)
@Service
public class NotificationService {
private MessageService messageService; // 非必需依赖
@Autowired(required = false) // 容器中没有也不报错
public void setMessageService(MessageService messageService) {
this.messageService = messageService;
}
}
// 3. 字段注入(不推荐)
@Service
public class OrderService {
@Autowired
private UserService userService; // 无法 final,测试困难
public void createOrder() {
userService.createUser(); // 空指针风险
}
}

测试对比

// 构造器注入:测试友好
@Test
public 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();
}
// 字段注入:测试困难
@Test
public void testCreateOrder() {
OrderService orderService = new OrderService();
// 需要反射注入依赖
ReflectionTestUtils.setField(orderService, "userService", mockUserService);
orderService.createOrder();
}

Q2:@Autowired、@Resource、@Inject 有什么区别?中频

Section titled “Q2:@Autowired、@Resource、@Inject 有什么区别?”

对比表格

注解来源注入策略指定 Bean适用场景
@AutowiredSpring先 byType,多个则 byName@Qualifier("beanName")Spring 项目首选
@ResourceJSR-250(Java 标准)先 byName,无则 byType@Resource(name="beanName")不依赖 Spring
@InjectJSR-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
@Service
public class OrderService {
@Autowired
@Qualifier("primaryUserService") // 指定 Bean 名称
private UserService userService;
}
// 多个实现,使用 @Primary 标注默认
@Service
@Primary // 优先注入
public class PrimaryUserService implements UserService {}
@Service
public class SecondaryUserService implements UserService {}
// @Resource 直接指定名称
@Service
public class OrderService {
@Resource(name = "primaryUserService")
private UserService userService;
}

链式追问四:BeanFactory vs ApplicationContext

Section titled “链式追问四:BeanFactory vs ApplicationContext”

Q1:BeanFactory 和 ApplicationContext 有什么区别?高频

Section titled “Q1:BeanFactory 和 ApplicationContext 有什么区别?”

对比表格

对比项BeanFactoryApplicationContext
定位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

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 延迟加载或重构代码消除循环依赖。