Spring 循环依赖难点分析

目录

循环依赖和三级依赖

Spring 三级缓存和循环依赖(紧密相关)

循环依赖
  • 指的是两个或多个Bean之间相互依赖,如Bean A依赖于Bean B,而Bean B又依赖于Bean A,则会出现循环依赖的情况
  • Spring默认采用Singleton模式,Bean默认是单例的,在容器启动时就会创建Bean实例,同时也会注入它所依赖的Bean实例
  • 当出现循环依赖的情况时,Spring的Bean实例化策略就会出现问题
  • 大家开发过程中好像对循环依赖这个概念无感知,这种错觉是因为工作在Spring的中,已经帮你解决了
解决方案
  • Spring引入了"提前暴露Bean"的机制,在创建A对象时,会先创建一个A的空对象并将其添加到缓存池中
  • 即"提前暴露Bean",然后继续创建B对象,将其注入A对象中
  • 在创建B对象时,由于A对象已经在缓存池中,可以直接获取到A对象,接着将B对象注入到A对象中,完成Bean的初始化
  • 注意:循环依赖的场景
    • 单例
      • 构造器循环依赖(构造器注入,循环依赖无法解决,日常开发不推荐构造器注入)
      • Field属性循环依赖(set方法注入,循环依赖问题可以解决,通过三级缓存)
    • 多例
      • prototype类型bean(循环依赖无法解决,构造函数和属性注入都不能解决循环依赖)
      • AbstractBeanFactory#doGetBean
缓存说明
  • 三级缓存,当Spring创建Bean的过程中,使用3个缓存存储已经创建的Bean实例,Map结构
    • 一级缓存(成熟的bean) Singletons object cache
      • singletonObjects 单例池 ,存储已经创建好的无代理的单例Bean对象,从该缓存中取出的 bean 可以直接使用
    • 二级缓存 Early-singletons object cache
      • earlySingletonObjects 提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依
    • 三级缓存 Singleton Factory object cache
      • singletonFactories 单例对象工厂的cache,存放 bean 工厂对象,解决循环依赖 避免Bean的实例化顺序出现问题
总结
  • Spring的三级缓存机制是为了避免Spring中的循环依赖问题而引入的
  • 也可以提高Bean的创建效率,避免创建重复的对象,从而提高应用程序的性能

三级缓存流程解析

Bean的创建流程

循环依赖代码案例

java 复制代码
@Service
public class OneServiceImpl implements OneService {
    @Autowired
    private TwoService twoService;
    ...
}
java 复制代码
@Service
public class TwoServiceImpl implements TwoService {
    @Autowired
    private OneService oneService;
    ...
}

关键类 DefaultSingletonBeanRegistry

bash 复制代码
  /**
   * 一级缓存,单例池 ,存储已经创建好的无代理的单例Bean对象,从该缓存中取出的 bean 可以直接使用
   * */
 Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

  /**
   * 三级缓存, 单例对象工厂的cache,存放 bean 工厂对象,解决循环依赖 避免Bean的实例化顺序出现问题。
   * */
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

  /**
   * 二级缓存, 提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依
   * */
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

  /** 用于保存已经创建的 Bean 的单例对象,能够确保应用程序上下文中每个单例 Bean 对象最多只创建一次,并且可以保证单例 Bean 的唯一性 */
Set<String> registeredSingletons = new LinkedHashSet<>(256);

  /** 表示bean创建过程中都会存储这里,创建完成后会被移除 */
Set<String> singletonsCurrentlyInCreation =  Collections.newSetFromMap(new ConcurrentHashMap<>(16));


//add 关键方法 
//把创建的bean加到一级缓存中,然后把二级和三级缓存的bean移除
addSingleton(String beanName, Object singletonObject)

//三级缓存中存储,二级缓存中移除bean ,ObjectFactory是函数式接口
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)

//get关键方法
//根据bean名称获取对应的bean,如果不存在则创建新的,ObjectFactory是调用外部传递进来的lambda表达式
getSingleton(String beanName, ObjectFactory<?> singletonFactory)

//用于从单例池中获取指定名称的单例 Bean 实例,方法内部实现了三级缓存查找机制
getSingleton(String beanName, boolean allowEarlyReference)

关键类 ObjectFactory

bash 复制代码
/**
 * 将创建对象的步骤封装到ObjectFactory中, 外部通过lambda表达式把相关创建步骤传递进来,返回创建好的对象
 /
@FunctionalInterface
public interface ObjectFactory<T> {
  T getObject() throws BeansException;
}

循环依赖流程图

java 复制代码
// 1、bean构建完毕,从singletonsCurrentlyIncreation集合中删除对应的beanName
afterSingletonCreation(beanName);
 
// 2、添加到单例缓存池中,并从二级缓存和三级缓存中删除
addSingleton(beanName, singletonObject);
java 复制代码
	// 添加到单例缓存池中,并从二级缓存和三级缓存中删除
	protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, singletonObject);
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

补充-Spring循环依赖异常

相关推荐
十碗饭吃不饱2 小时前
IDEA新建SpringBoot项目时没有低版本Java选项
java·spring boot·intellij-idea
林文韬3272 小时前
C、C++、Java 和 Python:四大编程语言的对比分析
java·c语言·c++·python
new_daimond2 小时前
设计模式-建造者模式详解
java·设计模式·建造者模式
Coding_Doggy2 小时前
java面试day4 | 微服务、Spring Cloud、注册中心、负载均衡、CAP、BASE、分布式接口幂等性、xxl-job
java·微服务·面试
郑洁文3 小时前
基于SpringBoot的实习管理系统设计与实现
java·spring boot·后端·spring
一條狗3 小时前
学习日报 20250921|MQ (Kafka)面试深度复盘
java·中间件·kafka
嚣张农民3 小时前
还在自己买服务器?试试 Amazon EC2,真香!
前端·后端·程序员
ytadpole3 小时前
揭秘设计模式:状态设计模式 优雅地管理对象状态
java·后端·设计模式
清风徐来QCQ3 小时前
关于maven编译没把resources资源包含进target目录
java·开发语言·maven