Spring循环依赖与解决方案全攻略

在Spring应用开发过程中,循环依赖(Circular Dependency)是一个既常见又棘手的问题。当Bean A依赖Bean B,同时Bean B又依赖Bean A时,就会形成经典的循环依赖场景。这种看似简单的依赖关系背后,隐藏着Spring框架精妙的设计哲学和复杂的解决机制。

一、循环依赖的本质矛盾

在传统Java开发中,对象间的循环依赖会导致严重的初始化问题:

class ServiceA {
    private ServiceB serviceB;
    // 构造器注入
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

class ServiceB {
    private ServiceA serviceA;
    // 构造器注入
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

这种情况下,无论是先实例化ServiceA还是ServiceB,都会陷入死循环。Spring通过三级缓存机制巧妙地解决了这个难题,但这种解决方案本身也存在边界条件。

二、Spring的三级缓存解密

Spring容器内部维护着三个重要的缓存容器:

  1. singletonObjects(一级缓存):存储完全初始化好的Bean
  2. earlySingletonObjects(二级缓存):存储提前暴露的原始Bean
  3. singletonFactories(三级缓存):存储Bean的ObjectFactory

Bean的创建过程遵循严格的生命周期:

// 简化版的Bean创建流程
protected Object doCreateBean(...) {
    // 1. 实例化
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    // 2. 加入三级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(...));
    // 3. 属性填充
    populateBean(beanName, mbd, instanceWrapper);
    // 4. 初始化
    exposedObject = initializeBean(...);
    return exposedObject;
}

三、构造器注入的循环依赖困局

当使用构造器注入时,循环依赖会直接导致BeanCurrentlyInCreationException:

// 构造器注入导致的循环依赖示例
@Component
public class CircularA {
    private final CircularB circularB;
    
    @Autowired
    public CircularA(CircularB circularB) {
        this.circularB = circularB;
    }
}

@Component
public class CircularB {
    private final CircularA circularA;
    
    @Autowired
    public CircularB(CircularA circularA) {
        this.circularA = circularA;
    }
}

Spring启动时会抛出:

Requested bean is currently in creation: Is there an unresolvable circular reference?

这是因为构造器注入需要在实例化阶段就完成依赖注入,而此时Bean尚未放入三级缓存。

四、Setter注入的破局之道

改用Setter方法注入可以解决这个问题:

@Component
public class CircularA {
    private CircularB circularB;
    
    @Autowired
    public void setCircularB(CircularB circularB) {
        this.circularB = circularB;
    }
}

@Component
public class CircularB {
    private CircularA circularA;
    
    @Autowired
    public void setCircularA(CircularA circularA) {
        this.circularA = circularA;
    }
}

这种方式的可行性来源于:

  1. 实例化与属性注入分离
  2. 提前暴露对象引用
  3. 分阶段完成依赖注入

五、复杂场景下的解决方案

5.1 @Lazy注解的巧妙运用

@Component
public class ServiceA {
    private final ServiceB serviceB;
    
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Lazy注解创建代理对象,延迟真实依赖的解析时机。

5.2 ApplicationContextAware接口

@Component
public class ServiceA implements ApplicationContextAware {
    private ApplicationContext context;
    
    public void doSomething() {
        ServiceB serviceB = context.getBean(ServiceB.class);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        this.context = ctx;
    }
}

5.3 ObjectProvider智能选择

@Component
public class ServiceA {
    private final ObjectProvider<ServiceB> serviceBProvider;
    
    public ServiceA(ObjectProvider<ServiceB> serviceBProvider) {
        this.serviceBProvider = serviceBProvider;
    }
    
    public void execute() {
        ServiceB serviceB = serviceBProvider.getIfAvailable();
    }
}

六、源码级的深度解析

分析DefaultSingletonBeanRegistry的关键代码:

public class DefaultSingletonBeanRegistry ... {
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
}

这段代码揭示了三级缓存的协同工作机制,以及Spring如何通过状态标记来检测循环依赖。

七、循环依赖的副作用与规避策略

  1. AOP代理的时序问题

    @Component
    public class ServiceA {
        @Autowired
        private ServiceB serviceB;
    
        @Transactional
        public void methodA() {
            // 事务注解可能失效
        }
    }
    

    当存在循环依赖时,AOP代理的创建时机可能导致注解失效。

  2. 设计模式改进方案

    • 引入中间层(Mediator Pattern)
    • 使用事件驱动(Event-Driven)
    • 领域拆分(Domain Separation)
  3. 架构层面的预防措施

    graph TD
        A[Controller层] --> B[Service层]
        B --> C[Manager层]
        C --> D[DAO层]
        D --> E[DB]
        style B fill:#f9f,stroke:#333
        style C fill:#ccf,stroke:#333
    

    通过分层架构约束依赖方向,形成严格的依赖层级。

八、Spring Boot中的特殊处理

在Spring Boot 2.6+版本中,可以通过配置关闭循环依赖支持:

spring.main.allow-circular-references=false

这个配置背后对应着Spring的AbstractAutowireCapableBeanFactory

public void setAllowCircularReferences(boolean allowCircularReferences) {
    this.allowCircularReferences = allowCircularReferences;
}

九、性能视角的循环依赖分析

通过JProfiler进行内存分析时,循环依赖可能导致:

  1. 对象图复杂度指数级增长
  2. GC Roots引用链过长
  3. 序列化/反序列化问题
  4. 缓存失效风险增加

十、新型解决方案探索

  1. 模块化改造

    @Configuration
    @Import({ModuleAConfig.class, ModuleBConfig.class})
    public class MainConfig {}
    
  2. 响应式编程范式

    @Service
    public class ReactiveServiceA {
        private final ReactiveServiceB serviceB;
    
        public Mono<String> process() {
            return serviceB.getData().flatMap(...);
        }
    }
    
  3. GraalVM原生镜像支持 在构建原生镜像时,循环依赖会导致更严重的初始化问题,需要特别处理。

结语:循环依赖的双面性

循环依赖既是设计缺陷的警示灯,也是框架能力的试金石。开发者应该:

  1. 理解底层解决机制
  2. 在架构设计阶段规避不必要的循环
  3. 合理使用框架提供的解决方案
  4. 建立代码健康度监控机制
正文到此结束
评论插件初始化中...
Loading...