当前位置: 首页 > article >正文

Java-37 深入浅出 Spring - IoC容器体系 循环依赖 原型Bean 原型作用域 Lazy ObjectFactory

点一下关注吧!!!非常感谢!!持续更新!!!

大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html

在这里插入图片描述

章节内容

上节我们完成了:

  • BeanFactory过程分析
  • Bean Lazy-Init

Spring IoC 循环依赖

基本介绍

循环依赖是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成环:
在这里插入图片描述
注意,这里不是函数的循环调用,是对象的相互依赖关系,循环调用其实是一个死循环,除非有终止条件。

Spring 中循环以来的场景有:

  • 构造器的循环依赖(构造器注入)
  • Field 属性的循环依赖(set 方法注入)

其中,构造器的循环依赖无法解决,只能抛出:BeanCurrentlyInCreationException 的异常,在解决属性循环以来的问题的时候,Spring 采用的是提前暴露对象的方法。

处理机制

  • 单例 Bean 构造器 参数循环依赖(无法解决)
  • prototype 原型 Bean 循环依赖(无法解决)

原型 Bean

对于原型 Bean 的初始化过程中不论是通过构造器参数循环依赖还是通过 setXxx 方法产生依赖,Spring 都会直接报错。

在 AbstractBeanFactory.doGetBean() 方法中有这么一段:
在这里插入图片描述
其中 isPrototypeCurrentlyInCreation 函数内容如下:

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

在获取 Bean 之前,如果这个 Bean 正在被创建则直接异常,原型 Bean 在创建之前会进行标记,表明这个 beanName 正在被创建吗,等创建结束之后就会删除标记。

这里就是创建之后,对 beanName 的移除。
在这里插入图片描述

总结一下:Spring 不支持原型 Bean 的循环依赖。

setXxx 与 Autowired

Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的应用时,对象的属性时可以延后设置的,但是构造器必须是在获取引用之前。
Spring 通过 setXxx 或者 Autowired 方法解决循环依赖其实是通过提前暴露一个 ObjectFactory 对象来完成的,简单来说 ClassA 在调用构造器完成对象初始化之后,在调用 ClassA 的 setClassB 方法之前就把 ClassA 实例化的对象通过 ObjectFactory 提前暴露到 Spring 容器。

Spring 容器初始化 ClassA 通过构造器初始化对象后提前暴露到 Spring 容器。

  • ClassA 调用 setClassB 方法,Spring 首先尝试从容器中获取 ClassB,此时 ClassB 不存在 Spring 容器中
  • Spring 容器初始化 ClassB,同时也会将 ClassB 提前暴露到 Spring 容器中
  • ClassB 调用 setClassA 方法,Spring 从容器中获取 ClassA,因为第一步中已经提前暴露了 ClassA,因此可以获取到 ClassA 实例
  • ClassA 和 ClassB 都完成了对象初始化操作,解决了循环依赖问题

最后小结

为什么原型无法解决循环依赖

在 Spring 中,当 Bean 被定义为 原型(Prototype)作用域 时,循环依赖问题无法解决。这是因为原型作用域的 Bean 不像单例作用域的 Bean 那样会被缓存,Spring 容器无法预先实例化并缓存它们的引用,从而在循环依赖场景下无法完成依赖注入。

单例作用域如何解决循环依

在单例作用域下,Spring 容器采用了三级缓存来解决循环依赖问题:

  • 第一级缓存:存储完全初始化好的单例 Bean(singletonObjects)。
  • 第二级缓存:存储提前暴露的 Bean 实例(earlySingletonObjects),避免完全初始化前的重复创建。
  • 第三级缓存:存储 Bean 的 ObjectFactory,用于动态创建 Bean。

在创建 Bean 时,如果发现循环依赖,Spring 可以通过提前暴露尚未完全初始化的 Bean 引用,解决循环依赖问题。

如何解决原型作用域的循环依赖?

由于 Spring 无法自动解决原型作用域的循环依赖问题,以下是一些常见的解决方法:

方法 1:重构设计,避免循环依赖

通常,循环依赖本身是一种设计问题,可以通过修改依赖关系来解决。例如:

  • 引入第三方服务作为中介。
  • 使用事件驱动或回调机制替代直接依赖。

方法 2:手动注入依赖

通过代码手动注入依赖,避免在 Bean 初始化时依赖注入。

@Component
@Scope("prototype")
public class A {
    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

方法 3:使用 @Lazy 延迟加载

通过延迟初始化的方式,让依赖在需要时才被创建,避免循环依赖在初始化时发生。

@Component
@Scope("prototype")
public class A {
    @Autowired
    @Lazy
    private B b;
}

@Component
@Scope("prototype")
public class B {
    @Autowired
    @Lazy
    private A a;
}

方法 4:通过 ObjectFactory 或 Provider 动态获取

使用 Spring 的 ObjectFactory 或 javax.inject.Provider 来延迟获取 Bean。

@Component
@Scope("prototype")
public class A {
    @Autowired
    private ObjectFactory<B> bFactory;

    public B getB() {
        return bFactory.getObject();
    }
}

@Component
@Scope("prototype")
public class B {
    @Autowired
    private ObjectFactory<A> aFactory;

    public A getA() {
        return aFactory.getObject();
    }
}

方法 4:通过 ObjectFactory 或 Provider 动态获取

使用 Spring 的 ObjectFactory 或 javax.inject.Provider 来延迟获取 Bean。

@Component
@Scope("prototype")
public class A {
    @Autowired
    private ObjectFactory<B> bFactory;

    public B getB() {
        return bFactory.getObject();
    }
}

@Component
@Scope("prototype")
public class B {
    @Autowired
    private ObjectFactory<A> aFactory;

    public A getA() {
        return aFactory.getObject();
    }
}


http://www.kler.cn/a/456927.html

相关文章:

  • officeweb365 弱口令登录
  • LeetCode - 初级算法 数组(存在重复元素)
  • 《向量数据库指南》——Milvus Cloud 2.5:Sparse-BM25引领全文检索新时代
  • vscode remote-ssh 免密登录不生效的问题
  • Python用K-Means均值聚类、LRFMC模型对航空公司客户数据价值可视化分析指标应用|数据分享...
  • (一)开发环境搭建以及配置
  • mongodb(6.0.15)安装注意事项,重装系统后数据恢复
  • 【Redis】Redis 典型应用 - 缓存 (cache)
  • Android 13 Launcher3 移除桌面抽屉入口
  • MLLM学习过程
  • 【Beats02】企业级日志分析系统ELK之Filebeat 收集日志及案例一
  • 机器视觉中的单线程、多线程与跨线程:原理与应用解析
  • 前端HTMLCSS
  • Eclipse 添加书签
  • 【SpringMVC】Bean 加载控制
  • 关于easy-es对时间范围查询遇到的小bug
  • 基于springboot校园志愿者管理系统源码和论文
  • Prompt提示工程上手指南(七)Prompt编写实战-基于智能客服问答系统下的Prompt编写
  • spring cloud微服务-OpenFeign的使用
  • AI主流向量数据库整理
  • 基于规则的系统架构:理论与实践
  • C语言中的贪心算法
  • BigDecimal解决精度问题
  • 【git】将项目上传到github、gitee
  • 【蓝桥杯每日一题】与或异或——DFS
  • 【Docker命令】如何使用 `docker cp` 命令拷贝容器文件到宿主机