由于工作中的一次报错 引起的对Spring循环依赖的探究

目录

什么是循环依赖

      1、自身依赖

                                

@Component
public class SelfDependencyBean {
    @Autowired
    private SelfDependencyBean selfDependencyBean;

    public void doSomething() {
        selfDependencyBean.doSomething();
    }
}

        2、两个Bean之间的循环依赖

                 

@Component
public class BeanA {
    @Autowired
    private BeanB beanB;
    public void doSomething() {
        beanB.doSomething();
    }
}

@Component
public class BeanB {
    @Autowired
    private BeanA beanA;
    public void doSomething() {
        beanA.doSomething();
    }
}

        3、多个Bean之间的循环依赖

       

@Component
public class BeanA {
    @Autowired
    private BeanB beanB;
    public void doSomething() {
        beanB.doSomething();
    }
}

@Component
public class BeanB {
    @Autowired
    private BeanC beanC;
    public void doSomething() {
        beanC.doSomething();
    }
}

@Component
public class BeanC {
    @Autowired
    private BeanA beanA;
    public void doSomething() {
        beanA.doSomething();
    }
}

        循环依赖是依赖注入框架(如Spring)中常见的问题,可能导致Bean创建失败或运行时错误,一般会抛出BeanCurrentlyInCreationException异常。

 

为什么会出现循环依赖

        以AB相互循环依赖(字段注入方式)为例:

                ①首先A进行实例化,然后进行属性赋值,发现A依赖于B,遂先去创建B

                ②然后B进行实例化,然后也会进行属性赋值,发现B又依赖于A,然后发现此时A是一个半成品状态,但是B需要一个完整的A,但是此时A在等待B,所以就出现了循环问题

                ③Spring 无法继续推进 A,B 的初始化,最终抛出BeanCurrentlyInCreationException,表示检测到循环依赖

 

什么情况下的循环依赖可以被处理

Spring解决循环依赖是有前置条件的:

        ①Bean必须是单例的

        ② 依赖注入的方式不能全是构造器注入

为什么都是构造器注入的时候,Spring无法解决循环依赖问题?

@Component
public class A {
    //构造器注入
    public A(B b) {
    }
}
 
@Component
public class B {
    //构造器注入
    public B(A a){
    }
}

因为构造器注入必须在实例化时完成依赖注入,无法提前暴露Bean引用,
而Spring解决循环依赖问题依靠的三级缓存在属性注入阶段,
也就是说调用构造函数时还未能放入三级缓存中,所以无法解决构造器注入的循环依赖问题。

 

Spring三级缓存是怎样解决循环依赖问题的

首先在Springboot2.6版本以上三级缓存是默认被关闭的,如需打开:

# application.yml
spring:
  main:
    allow-circular-references: true

        ①开始创建A-->实例化并且通过ObjectFactory暴露在三级缓存中-->属性填充B

        ②此时B还没有创建,所以要先创建B-->实例化B-->属性注入A,此时发现一级缓存中并没有A并且A还在创建过程中,所以就去三级缓存中找到工厂创建出一个半成品A-->把A放进二级缓存中-->B完成属性注入-->B初始化创建完成放入一级缓存中

        ③又回到创建A的过程-->继续属性填充B,此时一级缓存中有B-->初始化创建完成放入一级缓存,清空二级和三级缓存。

        B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?答:不会,虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。

 

为什么二级缓存不行

         不是二级缓存不行,如果Bean都不要AOP代理,二级缓存确实可以解决循环依赖问题。但在实际场景中,Spring必须支持AOP代理

  ①当Bean没有AOP代理的需求的时候,二级缓存是可以解决循环依赖的。

   ②当Bean需要AOP代理的时候,二级缓存会导致代理对象和原始对象引用不一致问题

    ③三级缓存就可以完美解决以上问题

        如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。      

 

记一次项目中的问题

         在SpringBoot2.3版本下,正常的循环依赖可以完美解决,但是当在方法上加@Transaction后就会启动报错。但是加上@Lazy之后,又正常启动。

@Service
public class BeanB {

    @Autowired
    private BeanA beanA;

    @Transactional
    public void Test(){
        System.out.println("222222");
    }
}

@Service
public class BeanA {

    @Autowired
    private BeanB beanB;

    @Transactional
    public void Test(){
        System.out.println("222222");
    }
}

@Service
public class BeanB {

    @Lazy
    @Autowired
    private BeanA beanA;

    @Transactional
    public void Test(){
        System.out.println("222222");
    }
}

@Service
public class BeanA {

    @Lazy
    @Autowired
    private BeanB beanB;

    @Transactional
    public void Test(){
        System.out.println("222222");
    }
}

//加了就正常启动

 

        而在SpringBoot2.6版本下,方法上直接加@Transaction也是可以正常启动。

 

本人实在没有找到明确的原因,只能看一下AI的回答

 

遗传算法优化BP神经网络(GABP)是一种结合了遗传算法(GA)和BP神经网络的优化预测方法。BP神经网络是一种多层前馈神经网络,常用于模式识别和预测问题,但其容易陷入局部最优。而遗传算法是一种模拟自然选择和遗传机制的全局优化方法,能够有效避免局部最优 。GABP算法通过遗传算法优化BP神经网络的权重和阈值,从而提高网络的学习效率和预测精度 。 种群:遗传算法中个体的集合,每个个体代表一种可能的解决方案。 编码:将解决方案转化为适合遗传操作的形式,如二进制编码。 适应度函数:用于评估个体解的质量,通常与目标函数相反,目标函数值越小,适应度越高。 选择:根据适应度保留优秀个体,常见方法有轮盘赌选择、锦标赛选择等。 交叉:两个父代个体交换部分基因生成子代。 变异:随机改变个体的部分基因,增加种群多样性。 终止条件:当迭代次数或适应度阈值达到预设值时停止算法 。 初始化种群:随机生成一组神经网络参数(权重和阈值)作为初始种群 。 计算适应度:使用神经网络模型进行训练和预测,根据预测误差计算适应度 。 选择操作:根据适应度选择优秀个体 。 交叉操作:对选择的个体进行交叉,生成新的子代个体 。 变异操作:对子代进行随机变异 。 替换操作:用新生成的子代替换掉一部分旧种群 。 重复步骤2-6,直到满足终止条件 。 适应度函数通常以预测误差为基础,误差越小,适应度越高。常用的误差指标包括均方根误差(RMSE)或平均绝对误差(MAE)等 。 GABP代码中包含了适应度函数的定义、种群的生成、选择、交叉、变异以及训练过程。代码注释详尽,便于理解每个步骤的作用 。 GABP算法适用于多种领域,如时间序列预测、经济预测、工程问题的优化等。它特别适合解决多峰优化问题,能够有效提高预测的准确性和稳定性 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值