问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

一文搞懂 Spring 循环依赖

创作时间:
作者:
@小白创作中心

一文搞懂 Spring 循环依赖

引用
CSDN
1.
https://blog.csdn.net/u012702547/article/details/139497162

Spring框架中的循环依赖是一个常见的面试话题,也是一个容易引发系统错误的技术难点。本文将深入探讨Spring如何处理循环依赖,以及在特定场景下循环依赖的解决方案。

一、循环依赖

1.1 什么是循环依赖

循环依赖指的是两个或多个Bean互相依赖的情况。例如:

@Service
public class AService {
    @Autowired
    BService bService;
}

@Service
public class BService {
    @Autowired
    AService aService;
}

这种依赖关系可以表示为:

1.2 循环依赖的类型

循环依赖主要有三种形态:

  1. 两个Bean互相依赖(如上例)
  2. 三个或更多Bean形成依赖环
  3. Bean自我依赖

一般来说,如果代码中出现循环依赖,说明设计上可能存在问题。虽然Spring默认可以处理循环依赖,但这种代码结构并不推荐。

二、循环依赖解决思路

2.1 解决思路

Spring通过引入三级缓存机制来解决循环依赖问题:

  1. earlySingletonObjects(二级缓存):存储通过反射创建但尚未完成初始化的Bean实例。
  2. singletonObjects(一级缓存):存储已完成初始化的Bean实例。
  3. singletonFactories(三级缓存):存储创建Bean实例的工厂对象,用于处理AOP代理。

具体流程如下:

  1. 创建AService实例时,先通过反射创建一个原始的AService对象,并存入二级缓存。
  2. 在给AService设置属性时,发现需要BService,于是创建BService。
  3. 创建BService时发现需要AService,从二级缓存中获取AService的原始对象使用。
  4. BService创建完成后,将其赋值给AService,此时AService和BService都创建完成。

2.2 存在AOP怎么办

当涉及AOP时,情况会更复杂。Spring通过三级缓存机制提前处理AOP,确保依赖关系正确:

  1. 在创建Bean时,如果需要AOP代理,先在三级缓存中保存一个生成代理对象的工厂。
  2. 在处理循环依赖时,从三级缓存中获取工厂生成代理对象,而不是直接使用原始Bean。
  3. 最终确保所有依赖都指向正确的代理对象。

2.3 小结

Spring解决循环依赖的关键在于:

  • 提前暴露:将未完成初始化的Bean提前暴露给其他Bean使用。
  • 提前AOP:在处理循环依赖时提前处理AOP代理。

三、特殊情况

3.1 基于构造器注入

如果依赖是通过构造器注入的,Spring无法解决循环依赖,因为创建Bean时就需要完整的依赖对象。例如:

@Service
public class AService {
    BService bService;
    public AService(BService bService) {
        this.bService = bService;
    }
}

@Service
public class BService {
    AService aService;
    public BService(AService aService) {
        this.aService = aService;
    }
}

3.2 prototype对象

当循环依赖的Bean作用域为prototype时,也会导致循环依赖失败,因为每次都需要现场创建Bean。

3.3 @Async

带有@Async注解的Bean产生循环依赖时,由于AOP处理的特殊性,Spring也无法自动解决。

四、@Lazy注解解决方案

通过添加@Lazy注解,可以解决上述三种特殊场景的循环依赖问题。@Lazy注解的工作原理是为依赖对象生成一个代理对象,延迟实际对象的加载。

例如:

@Service
public class AService {
    @Autowired
    @Lazy
    BService bService;
    @Async
    public void hello() {
        bService.hello();
    }
}

原理分析

@Lazy注解的处理主要发生在属性注入过程中:

  1. resolveFieldValue方法中,调用resolveDependency方法解析依赖。
  2. resolveDependency方法中,检查是否需要延迟加载(通过getLazyResolutionProxyIfNecessary方法)。
  3. 如果需要延迟加载,构建一个代理对象(通过buildLazyResolutionProxy方法)。

代理对象在需要时才会真正加载依赖Bean,从而避免了循环依赖问题。

总结

虽然Spring提供了多种机制来处理循环依赖,但在实际开发中,还是应该尽量避免循环依赖的出现,通过合理的模块划分和设计模式来优化代码结构。

本文内容参考自CSDN,原文链接:https://blog.csdn.net/u012702547/article/details/139497162

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号