AI摘要
我曾经以为三级缓存就是Spring解决循环依赖的"银弹",直到一次生产环境的Bean创建失败让我意识到,这套机制背后隐藏着更深刻的设计哲学。
记得在重构一个大型微服务模块时,我们遇到了一个诡异的启动错误:BeanCurrentlyInCreationException。奇怪的是,这个错误只在特定条件下出现,而且涉及的Bean明明是通过setter注入,理论上应该能被三级缓存解决。这次排查让我重新审视了Spring的三级缓存机制,也让我明白了它背后的真正价值。
一、从一个诡异的Bean创建异常说起
先来看看当时遇到问题的代码结构:
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("ServiceB注入完成");
}
@PostConstruct
public void init() {
System.out.println("ServiceA初始化完成");
}
}
@Service
public class ServiceB {
private ServiceC serviceC;
@Autowired
public void setServiceC(ServiceC serviceC) {
this.serviceC = serviceC;
System.out.println("ServiceC注入完成");
}
}
@Service
public class ServiceC {
private ServiceA serviceA; // 循环依赖!
@Autowired
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("ServiceA注入完成");
}
}理论上,这种setter注入的循环依赖应该能被Spring正常处理。但在加入AOP代理后,系统却抛出了异常。这让我开始怀疑:三级缓存真的只是为循环依赖而设计的吗?
二、循环依赖的本质:为什么这是个难题?
2.1 循环依赖的三种类型
// 1. 构造器循环依赖 - Spring无法解决
@Service
public class ConstructorCycleA {
private final ConstructorCycleB b;
public ConstructorCycleA(ConstructorCycleB b) { this.b = b; }
}
@Service
public class ConstructorCycleB {
private final ConstructorCycleA a;
public ConstructorCycleB(ConstructorCycleA a) { this.a = a; }
}
// 2. Setter循环依赖 - Spring可以解决
@Service
public class SetterCycleA {
private SetterCycleB b;
@Autowired public void setB(SetterCycleB b) { this.b = b; }
}
// 3. 字段注入循环依赖 - Spring可以解决
@Service
public class FieldCycleA {
@Autowired private FieldCycleB b;
}2.2 循环依赖的根本矛盾
循环依赖的核心问题是初始化顺序的悖论:
Bean A的创建需要Bean B,但Bean B的创建又需要Bean A。这就好比鸡生蛋还是蛋生鸡的问题。
在没有特殊机制的情况下,这种相互依赖会导致死锁。
三、三级缓存的全景解析:不只是循环依赖的解决方案
3.1 三级缓存的结构定义
让我们深入Spring源码,看看三级缓存的真实面貌:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry {
// 第一级缓存:存放完整的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 第二级缓存:存放早期的Bean引用(半成品)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 第三级缓存:存放Bean工厂,用于创建早期引用
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
// 正在创建中的Bean名称集合
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}3.2 三级缓存的工作流程
通过一个具体的例子来理解三级缓存如何协作:
@Component
public class UserService {
@Autowired private OrderService orderService;
}
@Component
public class OrderService {
@Autowired private UserService userService;
}创建流程分解:
开始创建UserService
// 1. 标记为创建中 singletonsCurrentlyInCreation.add("userService"); // 2. 实例化(调用构造器) UserService userService = new UserService(); // 3. 将Bean工厂放入三级缓存 addSingletonFactory("userService", () -> getEarlyBeanReference("userService", userService));属性注入时发现依赖OrderService
// 1. 查找orderService,发现不存在 // 2. 开始创建OrderService创建OrderService流程
// 1. 标记orderService为创建中 singletonsCurrentlyInCreation.add("orderService"); // 2. 实例化OrderService OrderService orderService = new OrderService(); // 3. 将OrderService工厂放入三级缓存 addSingletonFactory("orderService", () -> getEarlyBeanReference("orderService", orderService)); // 4. 属性注入时发现需要UserService解决循环依赖的关键时刻
// 1. 在三级缓存中找到了UserService的ObjectFactory ObjectFactory<?> singletonFactory = this.singletonFactories.get("userService"); // 2. 执行工厂方法获取早期引用 Object earlyReference = singletonFactory.getObject(); // 3. 将早期引用放入二级缓存,从三级缓存移除 this.earlySingletonObjects.put("userService", earlyReference); this.singletonFactories.remove("userService"); // 4. 将早期引用注入到OrderService
四、三级缓存的真正价值:AOP代理的优雅处理
现在来回答核心问题:三级缓存真的只是为了解决循环依赖吗?
答案是否定的。三级缓存的设计更多是为了优雅地处理AOP代理。
4.1 如果没有三级缓存,只有二级缓存会怎样?
假设我们只有二级缓存,看看AOP代理场景下的问题:
@Service
public class BusinessService {
@Autowired private AnotherService anotherService;
public void businessMethod() {
// 业务方法
}
}
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.example.BusinessService.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
// 日志逻辑
return joinPoint.proceed();
}
}在这种场景下,如果只有二级缓存:
- BusinessService实例化后,直接创建代理对象放入二级缓存
- 但当需要注入其他依赖时,可能代理对象的初始化还不完整
- 如果代理逻辑依赖其他Bean,就会出现问题
4.2 三级缓存如何优雅解决AOP问题
三级缓存通过ObjectFactory延迟代理对象的创建:
protected Object getEarlyBeanReference(String beanName, Object bean) {
Object exposedObject = bean;
if (!singletonsCurrentlyInCreation.contains(beanName)) {
return exposedObject;
}
// 关键:如果有必要,创建早期代理引用
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessors()) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return exposedObject;
}
}
return exposedObject;
}这样设计的好处:
- 延迟代理创建:只有在真正需要早期引用时才创建代理
- 保证代理一致性:无论从哪个路径获取Bean,得到的都是同一个代理对象
- 避免重复代理:防止同一个Bean被多次代理
五、从源码角度验证:AbstractAutowireCapableBeanFactory的创建逻辑
让我们深入Spring源码的关键方法:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1. 实例化Bean
Object beanInstance = instantiateBean(beanName, mbd);
// 2. 将Bean包装到ObjectFactory中,添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
// 3. 属性注入(可能触发循环依赖)
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 5. 处理循环依赖的早期引用
if (earlySingletonExposure) {
// 检查早期引用是否已经被使用
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 如果初始化后的Bean与早期引用相同,直接返回
if (exposedObject == beanInstance) {
exposedObject = earlySingletonReference;
}
// 处理AOP代理的特殊情况
else if (!this.allowRawInjectionDespiteWrapping &&
hasDependentBean(beanName)) {
// 验证依赖关系
}
}
}
return exposedObject;
}六、实际案例:三级缓存解决AOP代理的循环依赖
通过一个真实案例来理解三级缓存的价值:
@Service
@Transactional
public class TransactionalService {
@Autowired private AsyncService asyncService;
public void doSomething() {
// 事务操作
}
}
@Service
public class AsyncService {
@Autowired private TransactionalService transactionalService;
@Async
public void asyncMethod() {
transactionalService.doSomething(); // 这里需要的是代理对象!
}
}
@Aspect
@Component
public class MonitoringAspect {
@Around("execution(* com.example.*.*(..))")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
// 监控逻辑
return joinPoint.proceed();
}
}在这个复杂的场景中:
- TransactionalService需要被事务代理
- AsyncService需要被异步代理
- 两者存在循环依赖
- 同时还有全局的监控Aspect
三级缓存确保每个Bean只被代理一次,且所有依赖方得到的是正确的代理对象。
七、循环依赖的局限性:什么时候三级缓存也无能为力
7.1 构造器注入的循环依赖
@Service
public class ConstructorA {
private final ConstructorB b;
@Autowired
public ConstructorA(ConstructorB b) { // 无法解决!
this.b = b;
}
}
@Service
public class ConstructorB {
private final ConstructorA a;
@Autowired
public ConstructorB(ConstructorA a) { // 无法解决!
this.a = a;
}
}原因:构造器注入在实例化阶段就需要依赖对象,而此时Bean还没有被创建,无法放入三级缓存。
7.2 多例(Prototype)作用域的循环依赖
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeA {
@Autowired private PrototypeB b;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeB {
@Autowired private PrototypeA a;
}原因:多例Bean不缓存,每次都会重新创建,三级缓存机制不适用。
7.3 @Async方法的特殊限制
@Service
public class AsyncServiceA {
@Autowired private AsyncServiceB b;
@Async
public void asyncMethod() {
b.anotherAsyncMethod();
}
}
@Service
public class AsyncServiceB {
@Autowired private AsyncServiceA a; // 可能有问题!
@Async
public void anotherAsyncMethod() {
a.asyncMethod();
}
}八、最佳实践:避免和解决循环依赖的策略
8.1 设计层面的预防
// 不好的设计:双向紧密耦合
@Service
public class TightCouplingA {
@Autowired private TightCouplingB b;
}
@Service
public class TightCouplingB {
@Autowired private TightCouplingA a;
}
// 好的设计:通过接口解耦
public interface ServiceA {
void methodA();
}
public interface ServiceB {
void methodB();
}
@Service
public class ServiceAImpl implements ServiceA {
// 只依赖接口,不依赖具体实现
private final ServiceB serviceB;
public ServiceAImpl(ServiceB serviceB) {
this.serviceB = serviceB;
}
}8.2 使用@Lazy延迟注入
@Service
public class LazyServiceA {
// 延迟注入,打破循环依赖
@Lazy
@Autowired
private LazyServiceB b;
}
@Service
public class LazyServiceB {
@Autowired
private LazyServiceA a;
}8.3 应用事件解耦
@Service
public class EventDrivenA {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void doSomething() {
// 发布事件而不是直接调用
eventPublisher.publishEvent(new SomethingDoneEvent(this));
}
}
@Component
public class EventDrivenB {
@EventListener
public void handleEvent(SomethingDoneEvent event) {
// 响应事件
}
}九、深度排查:循环依赖问题的调试技巧
9.1 使用Spring的调试支持
# application.properties
# 启用循环依赖详细日志
logging.level.org.springframework.beans=DEBUG
# 显示Bean创建过程
debug=true9.2 自定义BeanPostProcessor进行调试
@Component
public class CycleDependencyDebugProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean初始化完成: " + beanName);
return bean;
}
}9.3 使用BeanFactory获取缓存状态
@Service
public class CacheInspector {
@Autowired
private ConfigurableListableBeanFactory beanFactory;
public void inspectCaches() {
if (beanFactory instanceof DefaultSingletonBeanRegistry) {
DefaultSingletonBeanRegistry registry =
(DefaultSingletonBeanRegistry) beanFactory;
// 通过反射查看缓存状态(生产环境慎用)
// 这里可以查看各级缓存的当前状态
}
}
}总结
经过这次深入的探究,我认识到三级缓存机制的设计远比表面看起来的精妙:
- 主要目的不是循环依赖:三级缓存的核心价值是优雅处理AOP代理,循环依赖只是其应用场景之一
- 保证代理一致性:确保在整个Bean生命周期中,无论何时获取Bean,得到的都是同一个代理实例
- 解决时序问题:通过延迟代理创建,解决了Bean初始化顺序与代理创建的矛盾
真正的启示:Spring框架的很多设计都是多维度的,表面解决一个问题,实则考虑了框架整体的扩展性、一致性和性能。
这次经历让我明白,理解一个框架不能停留在API层面,而要深入其设计哲学。三级缓存教会我的不是如何解决循环依赖,而是如何通过巧妙的设计在复杂约束条件下保持系统的优雅和健壮。
这种理解在后续学习Spring其他模块(如Spring Cloud、Spring Data)时给了我很大的帮助,让我能够更快地掌握复杂机制背后的核心思想。