Spring 如何解决循环依赖?

Spring如何解决循环依赖?

1. 什么是循环依赖?

以下是一个简单的例子:

@Component
public class BeanA {
    @Autowired
    private BeanB beanB;
}

@Component
public class BeanB {
    @Autowired
    private BeanA beanA;
}

这种循环依赖在Spring中分为三种情况:

  1. 构造器注入的循环依赖

    如果两个Bean通过构造器互相依赖,例如:

    public BeanA(BeanB beanB) { this.beanB = beanB; }
    public BeanB(BeanA beanA) { this.beanA = beanA; }
    
  2. Setter或字段注入的循环依赖

  3. Prototype作用域的循环依赖

2.Spring 如何解决循环依赖?

Spring通过三级缓存机制来解决单例Bean的Setter或字段注入类型的循环依赖问题。

2.1 核心类:DefaultSingletonBeanRegistry

在 DefaultSingletonBeanRegistry 中:


// 一级缓存:存放完全创建好的单例Bean
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存放正在创建过程中,提前暴露的Bean(解决循环依赖)
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:存放ObjectFactory,生产Bean实例的工厂(还没真正调用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

// 正在创建中的单例Bean名字(防止重复创建)
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

2.2 创建单例Bean的大致流程

1. 先检查缓存,看看有没有已经创建好的Bean

Object bean = this.singletonObjects.get(beanName);

如果有,直接返回。

2. 没有的话,标记正在创建中(防止别人也来抢)

this.singletonsCurrentlyInCreation.add(beanName);

3. 正式创建Bean

调用工厂方法(比如通过构造器、反射等方式创建对象实例)。

4. 处理三级缓存

Spring在执行实例化之后,属性注入之前,就会调用addSingletonFactory提前暴露自己。
位置在:
AbstractAutowireCapableBeanFactory#doCreateBean 中

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
1. earlySingletonExposure

表示是否允许早期暴露(一般是true)

2. addSingletonFactory(beanName, factory)

如果单例池(一级缓存)里还没有这个Bean,就把一个工厂(ObjectFactory)放到三级缓存里!

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}
3. getEarlyBeanReference是干嘛的?

factory.getObject() 内部调用 getEarlyBeanReference,可以对实例做加工(比如AOP代理)
这个方法一般用来处理:

  • 如果这个Bean要做AOP增强(比如加了@Transactional/@Async),就会在这里生成一个代理对象。

  • 如果不需要增强,直接返回原始Bean。

  • 所以,Spring暴露的不一定是原始Bean,可能是代理对象!

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}
  • 核心是调用所有SmartInstantiationAwareBeanPostProcessor类型的后处理器,比如AbstractAutoProxyCreator。

为什么要暴露ObjectFactory而不是直接暴露Object?
因为:

  • Bean还没完全创建完(比如AOP代理可能还没织入)
  • 用ObjectFactory可以延迟真正生成代理,只有有人依赖时再调用
  • 保持更高的灵活性
  • 暴露的是工厂,不是成品,后期可以灵活加工!

5. 完成后,把Bean放到一级缓存

this.singletonObjects.put(beanName, bean);

并且清除掉二级缓存、三级缓存、创建中标记。

2.3 解决循环依赖的关键流程

以下是Spring处理循环依赖的步骤:

  1. 创建Bean实例
    Spring在实例化一个Bean时,首先会在一级缓存中查找。如果未找到,会尝试通过三级缓存提前暴露一个工厂,用于生成当前正在创建的Bean的早期引用。

  2. 将早期引用暴露到三级缓存
    Spring在Bean实例化之后,尚未填充属性之前,会将Bean的ObjectFactory(一个创建Bean早期引用的工厂)存入三级缓存。

  3. 填充依赖
    在填充Bean的依赖时,如果发现需要依赖另一个Bean且该Bean正在创建中,Spring会从三级缓存中获取早期引用,并注入到当前Bean中。

  4. 完成初始化
    当Bean完成属性注入并经过初始化回调后,Spring会将完全初始化的Bean移入一级缓存,同时从二级和三级缓存中移除该Bean的引用。

1. 创建 A
2. 把 A 的 ObjectFactory 放入 singletonFactories(三级缓存)
3. 创建 B(因为 A 依赖 B)
4. 创建 B 的依赖 A → getSingleton("a")
   → 发现 singletonObjects 没有
   → 进入 singletonFactories 拿到 A 的早期代理引用(调用 ObjectFactory.getObject())
   → 得到增强后的代理对象 A'
   → 放入 earlySingletonObjects(二级缓存)
5. B 创建完成,注入的是 A'
6. 回到 A 创建流程,执行初始化完成
7. 把 A' 放入 singletonObjects(一级缓存)
8. 从 singletonFactories、earlySingletonObjects 中移除

2.4 为什么非得三个缓存都要有?

因为

  1. 要解决 构造函数注入 or @Autowired 的循环依赖(A 依赖 B,B 也依赖 A)

  2. 要同时兼容 AOP 代理(Spring 在 Bean 初始化阶段可能需要生成代理)

1. 只有一级缓存 + 三级缓存

  1. 创建 A:

    • 把一个 ObjectFactory 放入 singletonFactories
  2. 创建 B:

    • 发现 B 依赖 A → 从 singletonFactories 调用 getObject() 得到 A 的早期引用(可能是代理)
  3. 再次其他人访问 A:

    • 又调用 getObject() → 又生成了一个 A 的早期引用(可能是另一个代理)

问题出现:

  • 每次访问早期引用,都要重新调用 getObject(),可能会生成多个代理对象!

  • BeanPostProcessor 如果有副作用(比如懒加载、计数器),也会被重复执行

  • 导致系统行为不一致、性能差、潜在 bug

为什么需要二级缓存?

  • 它是三级缓存到一级缓存之间的过渡层,用于缓存已经暴露出去的早期 Bean 引用(可能是代理对象),防止重复调用三级缓存生成代理对象。

  • 把 getObject() 的结果缓存到 earlySingletonObjects(二级缓存),后续直接返回保证整个生命周期内 早期引用一致且只创建一次!

2. 只有一级缓存 + 二级缓存

  1. 创建 A,new 出实例

  2. 将原始实例暴露到二级缓存

  3. 创建 B,依赖 A → 从二级缓存拿到 A 的原始对象(不是代理)

  4. B 注入了一个非增强的 A

  5. 回到 A,生成代理对象并完成初始化 → 注册到一级缓存

问题出现:

  • B 拿到的 A 是原始对象,AOP、事务等增强完全失效!
  • 整个 Spring AOP、声明式事务、缓存等机制会在循环依赖中失效

为什么需要三级缓存?

  • 支持AOP代理的动态生成,解决代理Bean和循环依赖之间的矛盾。
  • 避免提前暴露不完整的Bean,确保Bean生命周期管理的规范性。
  • 提供了一个灵活的机制,可以在不同场景下按需生成不同形式的Bean(原始Bean或代理Bean)。

3.如何解决循环依赖问题?

  1. 优化设计,避免循环依赖:
    重新审视依赖关系,分解职责,消除不必要的循环依赖。

  2. 使用@Lazy注解:
    标注依赖项为懒加载,避免在Bean初始化时立刻注入。

    @Component
    public class BeanA {
        @Autowired
        @Lazy
        private BeanB beanB;
    }
    
  3. 使用接口拆分:
    如果两个Bean存在紧耦合,可以引入接口或事件机制,降低直接依赖。

  4. 明确指定Bean的初始化顺序:
    使用@DependsOn注解显式声明依赖顺序,但这仅适用于某些特定场景。

posted @ 2024-12-15 20:50  Eiffelzero  阅读(206)  评论(0)    收藏  举报