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循环依赖异常

相关推荐
uzong1 小时前
一次慢接口背后,竟藏着40+种可能!你中过几个
后端·面试·程序员
G探险者2 小时前
滴滴P0级故障背后:互联网公司是如何分级处理线上事故的?
后端
G探险者2 小时前
从 Tomcat 与 Jetty 的对比,聊聊影响一个服务并发能力的关键因素
后端
你的人类朋友2 小时前
“签名”这个概念是非对称加密独有的吗?
前端·后端·安全
南尘NCA86662 小时前
企业微信防封防投诉拦截系统:从痛点解决到技术实现
java·网络·企业微信
幼稚园的山代王3 小时前
go语言了解
开发语言·后端·golang
kkjt01303 小时前
{MySQL查询性能优化索引失效的八大场景与深度解决方案}
后端
怪兽20143 小时前
SQL优化手段有哪些
java·数据库·面试
ss2733 小时前
手写MyBatis第107弹:@MapperScan原理与SqlSessionTemplate线程安全机制
java·开发语言·后端·mybatis
橙子家4 小时前
log4net 简介以及简单示例(.net8)
后端