问题引入
2023 年春,某团队将一个单体应用拆分为微服务,新服务启动时频繁报错 BeanCurrentlyInCreationException:
Error creating bean with name 'orderService':
Bean with name 'paymentService' has been injected into other beans
[paymentService] in its raw version as part of a circular reference,
but has eventually been wrapped (for example by a proxy).
技术团队排查后发现,OrderService 通过构造器注入 PaymentService,PaymentService 又通过构造器注入 OrderService。开发者的第一反应是"循环依赖不是 Spring 自动解决的吗?"——但构造器循环依赖和 Setter 循环依赖的待遇完全不同。
这不是个例。Spring 面试几乎必从"什么是 IOC"开始,但追问下去:
- Bean 是怎么从 class 文件变成容器中的对象的?每一步做了什么?
- 循环依赖怎么解决的?三级缓存是哪三级?分别存了什么?
- 为什么用三级缓存,两级行不行?
- @Autowired 和 @Resource 有什么区别?构造器注入和 Setter 注入哪个更好?
- prototype 作用域的 Bean 为什么不能用 @PreDestroy?
本文从 Bean 的生命周期出发,穿透 IOC 容器的每一个环节,揭示循环依赖的解决机制与三级缓存的设计哲学。
核心概念
1. IOC 与 DI:控制反转和依赖注入
**IOC(Inversion of Control,控制反转)**是一种设计思想——对象的创建和依赖关系的管理从"对象自己负责"反转给"外部容器负责"。
**DI(Dependency Injection,依赖注入)**是 IOC 的实现方式,有三种注入方式:
| 注入方式 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| 构造器注入 | public OrderService(PaymentService ps) |
依赖明确、不可变、循环依赖可检测 | 参数多时代码冗长 |
| Setter 注入 | setPaymentService(PaymentService ps) |
可选依赖、灵活 | 对象可能处于不完整状态 |
| 字段注入 | @Autowired private PaymentService ps |
简洁 | 隐藏依赖、测试困难、无法保证不可变性 |
Spring 官方从 4.x 开始推荐构造器注入,SpringBoot 的 spring-boot-starter 甚至会在检测到字段注入时打印警告。
2. BeanDefinition:Bean 的设计图纸
BeanDefinition 不是 Bean 本身,而是 Bean 的"元信息描述",Spring 容器先读取配置生成 BeanDefinition,再根据图纸创建 Bean。
配置来源 BeanDefinition Bean 实例
┌──────────┐ ┌──────────────────┐ ┌──────────┐
│ XML配置 │ 解析 │ beanClass: │ 创建 │ Order │
│ @Component│ ──────────▶ │ OrderService │ ──────────▶ │ Service │
│ @Bean │ │ scope: singleton │ │ 实例 │
│ │ │ lazyInit: false │ │ │
└──────────┘ │ dependsOn: [] │ └──────────┘
└──────────────────┘
读图导引:配置来源(XML、注解、Java Config)被解析为 BeanDefinition,BeanDefinition 是"图纸",最终根据图纸创建 Bean 实例。注意一张图纸可以创建多个实例(如 prototype 作用域)。
核心属性:
beanClass:Bean 的全限定类名scope:作用域(singleton、prototype 等)lazyInit:是否延迟初始化dependsOn:显式依赖的 Bean 名称autowireMode:自动装配模式(byType、byName、constructor)initMethodName/destroyMethodName:自定义初始化和销毁方法
3. BeanFactory vs ApplicationContext
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 本质 | 基础 IOC 容器 | 高级 IOC 容器 |
| 加载方式 | 延迟加载(getBean 时才创建) | 预加载(refresh 时创建所有单例) |
| 事件发布 | 不支持 | 支持(ApplicationEvent) |
| 资源加载 | 不支持 | 支持(ResourceLoader) |
| 国际化 | 不支持 | 支持(MessageSource) |
| AOP 集成 | 需手动配置 | 原生支持 |
ApplicationContext 继承自 BeanFactory,并扩展了更多企业级功能。生产环境几乎总是使用 ApplicationContext(如 AnnotationConfigApplicationContext、ClassPathXmlApplicationContext)。
4. Bean 作用域
| 作用域 | 说明 | 适用场景 |
|---|---|---|
| singleton(默认) | 每个 Spring 容器只有一个实例 | 无状态 Service、DAO |
| prototype | 每次请求创建新实例 | 有状态对象(如购物车) |
| request | 每个 HTTP 请求一个实例 | Web 应用中的请求级数据 |
| session | 每个 HTTP Session 一个实例 | 用户登录信息 |
| application | 每个 ServletContext 一个实例 | 全局配置 |
| websocket | 每个 WebSocket 连接一个实例 | 实时通信 |
prototype 的陷阱:Spring 只负责创建 prototype Bean,不管理其生命周期。如果 prototype Bean 依赖了 singleton Bean,singleton Bean 持有的 prototype Bean 引用不会更新——每次注入的还是同一个 prototype 实例。要解决这个问题需要使用 ObjectFactory 或 @Lookup 方法。
原理分析
1. Bean 生命周期完整链路
┌─────────────────────────────────────────────────────────────────────────┐
│ Spring Bean 生命周期 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ① 实例化 ② 属性填充 ③ 初始化 │
│ Instantiation Population Initialization │
│ │
│ ┌─────────┐ ┌───────────┐ ┌─────────────────────────────────┐ │
│ │ 反射调用 │ │ @Autowired │ │ BeanNameAware.setBeanName() │ │
│ │ 构造器 │────▶│ 依赖注入 │────▶│ BeanFactoryAware.setBeanFactory()│ │
│ │ │ │ │ │ ApplicationContextAware... │ │
│ └─────────┘ └───────────┘ ├─────────────────────────────────┤ │
│ │ BeanPostProcessor. │ │
│ │ postProcessBeforeInit() │ │
│ ├─────────────────────────────────┤ │
│ │ @PostConstruct │ │
│ │ InitializingBean.afterProperties()│ │
│ │ init-method │ │
│ ├─────────────────────────────────┤ │
│ │ BeanPostProcessor. │ │
│ │ postProcessAfterInit() │ │
│ │ ← AOP 代理在此创建 │ │
│ └─────────────────────────────────┘ │
│ │
│ ④ 使用 ⑤ 销毁 │
│ In Use Destruction │
│ │ │
│ ▼ │
│ @PreDestroy │
│ DisposableBean.destroy() │
│ destroy-method │
│ │
└─────────────────────────────────────────────────────────────────────────┘
读图导引:生命周期分五大阶段——实例化(反射创建对象)、属性填充(依赖注入)、初始化(Aware 回调 + 前后处理器 + 初始化方法)、使用、销毁。重点关注初始化阶段,这是 AOP 代理创建和自定义逻辑介入的关键时机。
详细步骤解析:
① 实例化(Instantiation)
Spring 通过反射调用 Bean 的构造器创建对象:
java
// 简化的创建逻辑
Object bean = beanClass.getDeclaredConstructor().newInstance();
此时对象已创建,但属性都还是默认值(null、0、false)。
② 属性填充(Population)
Spring 解析 BeanDefinition 中的属性定义,通过反射注入依赖:
java
// 简化的依赖注入
for (PropertyValue pv : beanDefinition.getPropertyValues()) {
Field field = beanClass.getDeclaredField(pv.getName());
field.setAccessible(true);
field.set(bean, resolveValue(pv.getValue())); // 解析并注入值
}
@Autowired 的解析由 AutowiredAnnotationBeanPostProcessor 完成,它会扫描字段和方法上的 @Autowired 注解,从容器中查找匹配的 Bean 注入。
③ 初始化(Initialization)
初始化阶段是 Bean 真正"就绪"前的准备过程,分为三个子阶段:
Aware 接口回调:如果 Bean 实现了 Aware 接口,Spring 会注入容器相关信息:
BeanNameAware.setBeanName():注入 Bean 的名称BeanFactoryAware.setBeanFactory():注入 BeanFactoryApplicationContextAware.setApplicationContext():注入 ApplicationContext
BeanPostProcessor 前置处理:所有注册的 BeanPostProcessor.postProcessBeforeInitialization() 被调用。
初始化方法:三种方式按顺序执行:
@PostConstruct(JSR-250 注解,由CommonAnnotationBeanPostProcessor处理)InitializingBean.afterPropertiesSet()(Spring 接口)- 自定义
init-method(XML 或 @Bean(initMethod) 配置)
BeanPostProcessor 后置处理:postProcessAfterInitialization() 被调用。这是AOP 代理创建的关键时机——如果 Bean 需要被代理,AbstractAutoProxyCreator 在此生成代理对象替代原始 Bean。
④ 使用(In Use)
Bean 被放入 singletonObjects(一级缓存),可以被其他 Bean 注入或从容器中获取。
⑤ 销毁(Destruction)
容器关闭时,按逆序调用销毁方法:
@PreDestroy(JSR-250)DisposableBean.destroy()(Spring 接口)- 自定义
destroy-method
2. 循环依赖与三级缓存
什么是循环依赖
java
@Component
public class OrderService {
@Autowired
private PaymentService paymentService; // OrderService 依赖 PaymentService
}
@Component
public class PaymentService {
@Autowired
private OrderService orderService; // PaymentService 依赖 OrderService
}
两个 Bean 互相依赖,形成闭环。如果没有任何特殊处理,创建 A 需要 B,创建 B 需要 A,永无止境。
三级缓存的结构
java
// DefaultSingletonBeanRegistry 中的三个缓存
/** 一级缓存:成品 Bean,完全初始化好的单例对象 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级缓存:半成品 Bean,已实例化但未填充属性的早期引用 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** 三级缓存:Bean 工厂,用于生成早期引用(可能包含 AOP 代理逻辑) */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
三级缓存解决循环依赖的过程
创建 OrderService(A) 创建 PaymentService(B)
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 1. 实例化 A │ │ 1. 实例化 B │
│ (new A) │ │ (new B) │
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 2. 将 A 放入 │ │ 2. 将 B 放入 │
│ 三级缓存 │ │ 三级缓存 │
│ (ObjectFactory)│ │ (ObjectFactory)│
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 3. 填充属性 │ │ 3. 填充属性 │
│ 需要 B │◀────────────────────────│ 需要 A │
└──────┬──────┘ └──────┬──────┘
│ │
│ 4. 从三级缓存获取 B │ 4. 从三级缓存获取 A
│ (调用工厂创建) │ (调用工厂获取早期引用)
│◀───────────────────────────────────────│
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 5. B 完成创建 │ │ 5. A 拿到 B 的早期引用
│ 进入一级缓存│ │ 继续填充属性
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 6. A 继续初始化│ │ 6. A 完成创建 │
│ 使用完整的 B│ │ 进入一级缓存│
└─────────────┘ └─────────────┘
读图导引:关键在第 4 步——B 在填充属性时需要 A,此时 A 还在创建中(未完成初始化),Spring 从三级缓存中取出 A 的 ObjectFactory,调用它获取 A 的"早期引用"(early reference)注入给 B。B 完成后,A 继续初始化,最终拿到的是完整的 B。
为什么必须是三级缓存?两级行不行?
这是面试的终极追问。
如果只使用两级缓存(去掉三级缓存,只用一级 + 二级):
java
// 假设只有一级和二级
// 实例化 A 后,直接将 A 的早期引用放入二级缓存
earlySingletonObjects.put("a", a);
问题在于AOP 代理。如果 A 需要被 AOP 代理,那么注入给 B 的应该是 A 的代理对象,而不是原始 A 对象。
在 Spring 中,AOP 代理的创建时机是在 BeanPostProcessor.postProcessAfterInitialization()(初始化后)。也就是说:
- A 实例化后、初始化前:只有原始对象
- A 初始化后:可能变成代理对象
如果直接把原始 A 放入二级缓存,B 注入的就是原始 A,而不是代理后的 A。这会导致:B 中注入的 A 和最终容器中的 A 不是同一个对象(一个是原始对象,一个是代理对象)。
三级缓存的设计哲学:
java
// 三级缓存存的是工厂,不是对象本身
singletonFactories.put(beanName, () -> {
// 这个工厂在第一次被调用时,会检查是否需要创建代理
// 如果需要,返回代理后的早期引用
// 如果不需要,返回原始对象的早期引用
return getEarlyBeanReference(beanName, mbd, bean);
});
三级缓存中的 ObjectFactory 是一个延迟创建的工厂:
- 如果没有循环依赖,这个工厂永远不会被调用,AOP 代理在正常的初始化后阶段创建
- 如果有循环依赖,工厂被提前调用,此时会触发
SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference(),让 AOP 代理有机会提前创建
两级缓存的 workaround:如果去掉三级缓存,直接把 getEarlyBeanReference() 的结果放入二级缓存,会导致所有 Bean 都在实例化后立刻创建 AOP 代理,违背了"代理在初始化后创建"的设计,也可能导致 BeanPostProcessor 的执行顺序问题。
所以:三级缓存是 Spring 为了兼顾循环依赖解决和 AOP 代理正确性而设计的精妙结构。
构造器循环依赖:三级缓存也无能为力
java
@Component
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) { // 构造器注入
this.paymentService = paymentService;
}
}
@Component
public class PaymentService {
private final OrderService orderService;
public PaymentService(OrderService orderService) { // 构造器注入
this.orderService = orderService;
}
}
为什么构造器循环依赖无法解决?
因为构造器注入发生在实例化阶段,此时对象还没创建完成,连早期引用都不存在。Spring 尝试创建 A → 发现需要 B → 尝试创建 B → 发现需要 A → A 还在创建中但三级缓存里还没有(因为实例化还没完成)。
解决方案:
- 改用 Setter 注入或字段注入(让 Spring 用三级缓存解决)
- 使用
@Lazy延迟注入(注入的是代理对象,真正使用时才创建目标 Bean) - 重构代码,打破循环依赖(推荐,从根本上解决问题)
3. @Autowired 与 @Resource 的底层差异
| 维度 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 注解 | JSR-250 标准注解 |
| 匹配规则 | 先按类型(Type),再按名称 | 先按名称(Name),再按类型 |
| required | 支持 required = false |
不支持(可通过 name 指定) |
| 作用位置 | 构造器、方法、字段、参数 | 字段、方法 |
@Autowired 的解析过程:
- 按类型查找所有匹配的 Bean
- 如果只有一个,直接注入
- 如果有多个,按字段名/参数名匹配 Bean 名称
- 如果还匹配不上,抛出
NoUniqueBeanDefinitionException - 可以用
@Primary标注首选 Bean,或用@Qualifier指定名称
@Resource 的解析过程:
- 先按
name属性查找(默认是字段名/方法名) - 如果找不到,再按类型查找
实战/源码
1. Bean 生命周期验证代码
java
@Component
public class LifecycleBean implements BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
public LifecycleBean() {
System.out.println("① 构造器:实例化");
}
@Autowired
public void setDependency(String dependency) {
System.out.println("② Setter:属性填充");
}
@Override
public void setBeanName(String name) {
System.out.println("③ BeanNameAware:" + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("④ BeanFactoryAware");
}
@Override
public void setApplicationContext(ApplicationContext context) {
System.out.println("⑤ ApplicationContextAware");
}
@PostConstruct
public void postConstruct() {
System.out.println("⑥ @PostConstruct");
}
@Override
public void afterPropertiesSet() {
System.out.println("⑦ InitializingBean.afterPropertiesSet()");
}
public void initMethod() {
System.out.println("⑧ init-method");
}
@PreDestroy
public void preDestroy() {
System.out.println("⑨ @PreDestroy");
}
@Override
public void destroy() {
System.out.println("⑩ DisposableBean.destroy()");
}
public void destroyMethod() {
System.out.println("⑪ destroy-method");
}
}
输出顺序:
① 构造器:实例化
② Setter:属性填充
③ BeanNameAware:lifecycleBean
④ BeanFactoryAware
⑤ ApplicationContextAware
⑥ @PostConstruct
⑦ InitializingBean.afterPropertiesSet()
⑧ init-method
... Bean 就绪 ...
⑨ @PreDestroy
⑩ DisposableBean.destroy()
⑪ destroy-method
2. 三级缓存核心源码解析
java
// DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 先从一级缓存拿成品 Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 2. 一级没有,从二级缓存拿早期引用
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 二级也没有,从三级缓存拿工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 4. 调用工厂创建早期引用
singletonObject = singletonFactory.getObject();
// 5. 提升到二级缓存(从三级移除)
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
关键逻辑:
- 一级缓存
singletonObjects:存完全初始化好的 Bean(成品) - 二级缓存
earlySingletonObjects:存已实例化但未初始化的 Bean(半成品),从三级缓存晋升而来 - 三级缓存
singletonFactories:存工厂,用于生成早期引用(可能包含 AOP 代理逻辑)
3. 循环依赖排查与解决
排查方法:
bash
# 启动时添加 DEBUG 日志,查看 Bean 创建过程
java -jar app.jar --debug
# 或 application.properties
logging.level.org.springframework.beans.factory.support=DEBUG
解决方案对比:
| 场景 | 方案 | 代码 |
|---|---|---|
| Setter/字段循环依赖 | Spring 自动解决(三级缓存) | 无需改动 |
| 构造器循环依赖 | 改用 Setter 注入 | 去掉 final,加 @Autowired Setter |
| 构造器循环依赖 | @Lazy 延迟注入 | public A(@Lazy B b) |
| 构造器循环依赖 | 重构打破循环 | 提取公共逻辑到 C |
@Lazy 的原理:
java
@Component
public class OrderService {
private final PaymentService paymentService;
public OrderService(@Lazy PaymentService paymentService) {
this.paymentService = paymentService;
}
}
@Lazy 注入的是目标 Bean 的代理对象(TargetSource proxy),真正调用方法时才从容器中获取真实 Bean。这样就打破了"创建 A 需要创建 B"的循环——A 只需要 B 的代理即可实例化。
4. prototype 作用域的正确用法
java
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
// ...
}
@Service
public class OrderService {
// 错误:只会在注入时创建一次 prototype Bean
@Autowired
private ShoppingCart cart;
// 正确:每次使用时获取新的 prototype Bean
@Autowired
private ObjectFactory<ShoppingCart> cartFactory;
public void addItem(Item item) {
ShoppingCart cart = cartFactory.getObject(); // 每次获取新实例
cart.add(item);
}
}
常见问题
Q1:@Autowired 和 @Resource 用哪个?
答:优先用 @Autowired(Spring 生态更自然),但两者都可以。关键区别:
@Autowired先按类型匹配,类型冲突时用名称@Resource先按名称匹配,名称找不到时按类型
如果容器中有多个同类型 Bean(如多个 DataSource),@Autowired 需要配合 @Qualifier,而 @Resource 可以直接用 name 属性:
java
@Autowired
@Qualifier("masterDataSource")
private DataSource dataSource;
// 等价于
@Resource(name = "masterDataSource")
private DataSource dataSource;
Q2:构造器注入和 Setter 注入哪个更好?
答:Spring 官方推荐构造器注入,原因:
- 依赖不可变:字段可以声明为
final,保证对象创建后依赖不会被修改 - 依赖明确:构造器参数一目了然,知道 Bean 需要什么
- 循环依赖可检测:构造器循环依赖会在启动时直接报错,而不是在运行时才发现
- 测试友好:单元测试时可以直接
new Service(mockDep),不需要反射
Setter 注入适用于可选依赖——依赖有默认值,不设置也能正常工作。
字段注入虽然简洁,但隐藏了依赖关系,不利于测试和代码审查,SpringBoot 甚至会在检测到字段注入时打印警告:
@Autowired fields are not recommended
Q3:为什么 SpringBoot 2.x 推荐使用构造器注入,但 @Autowired 可以省略?
答:Spring 4.3+ 开始,如果 Bean 只有一个构造器,@Autowired 可以省略,Spring 会自动用它作为注入构造器。
java
@Service
public class OrderService {
private final PaymentService paymentService;
// 只有一个构造器,@Autowired 可省略
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
但如果有多个构造器,必须用 @Autowired 标注要用于注入的那个:
java
@Service
public class OrderService {
private PaymentService paymentService;
private CacheManager cacheManager;
@Autowired // 标注这个构造器用于依赖注入
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public OrderService(PaymentService paymentService, CacheManager cacheManager) {
this.paymentService = paymentService;
this.cacheManager = cacheManager;
}
}
Q4:三级缓存中的 ObjectFactory 什么时候会被调用?
答:只有在发生循环依赖时才会被提前调用。
正常流程(无循环依赖):
- 实例化 A
- 将 A 的 ObjectFactory 放入三级缓存
- 填充 A 的属性(不需要其他 Bean,或需要的 Bean 已在一级缓存)
- 初始化 A(包括 AOP 代理创建)
- 将成品 A 放入一级缓存,从三级缓存移除 ObjectFactory
循环依赖流程:
- 实例化 A
- 将 A 的 ObjectFactory 放入三级缓存
- 填充 A 的属性,发现需要 B
- 创建 B,B 填充属性时需要 A
- 从三级缓存调用 A 的 ObjectFactory,获取 A 的早期引用
- B 完成创建,进入一级缓存
- A 继续初始化,完成创建,进入一级缓存
Q5:prototype 作用域的 Bean 为什么没有销毁回调?
答:因为 Spring 不管理 prototype Bean 的生命周期。
singleton Bean 的销毁由容器统一管理(容器关闭时逐个调用销毁方法),但 prototype Bean 创建后就交给调用者了,Spring 容器不持有它的引用,也不知道它什么时候该销毁。
如果 prototype Bean 需要释放资源,调用者需要自己管理:
java
@Component
public class PrototypeBean {
private Connection connection;
@PostConstruct
public void init() {
connection = dataSource.getConnection();
}
// 自定义释放方法,调用者负责调用
public void release() {
if (connection != null) {
connection.close();
}
}
}
或者用 DisposableBeanAdapter 注册自定义销毁回调(较复杂,不推荐)。
总结
Spring IOC 容器的核心不是"把对象交给容器管"这句口号,而是 Bean 从 class 到可用对象的完整生命周期管理:
- BeanDefinition 是图纸,不是房子:Spring 先解析配置生成 BeanDefinition,再根据图纸创建 Bean,这个分层设计让配置和实例化解耦
- Bean 生命周期的关键是初始化阶段:Aware 回调让 Bean 感知容器,BeanPostProcessor 让框架和开发者能在初始化的前后插入逻辑,AOP 代理就在后置处理中创建
- 三级缓存是解决循环依赖的精妙设计:一级存成品、二级存半成品、三级存工厂,三级缓存中的 ObjectFactory 不仅提供早期引用,还承担了 AOP 代理提前创建的责任,两级缓存无法同时满足循环依赖和代理正确性
- 构造器循环依赖是设计问题,不是框架问题:Spring 不解决构造器循环依赖,因为实例化阶段就需要依赖对象,此时早期引用还不存在。遇到构造器循环依赖应该反思设计,或者用 @Lazy 延迟注入作为临时方案
- 注入方式的选择有明确的最佳实践:构造器注入优于 Setter 注入,Setter 注入优于字段注入;构造器注入让依赖明确、不可变、可测试
理解 IOC 容器后,下一个追问自然指向 AOP——因为 AOP 代理正是在 Bean 生命周期的 BeanPostProcessor.postProcessAfterInitialization() 中创建的。理解容器是理解 AOP 的前提。