Spring的循环依赖问题

问题提出

Spring中代码-产生循环依赖问题
@Component
class A{

    private B b;

    public A(B b) {
        this.b = b;
    }
}

@Component
class B{

    private A a;

    public B(A a) {
        this.a = a;
    }
}

报错

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-05-26 12:46:05.173 ERROR 10804 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  a defined in file [C:\Users\lianlei\IdeaProjects\videowebsite\target\classes\com\ll\videowebsite\utils\A.class]
↑     ↓
|  b defined in file [C:\Users\lianlei\IdeaProjects\videowebsite\target\classes\com\ll\videowebsite\utils\B.class]
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

问题:

The dependencies of some of the beans in the application context form a cycle:

循环依赖注入,主要是因为,使用构造方法进行注入,A对象初始化的时候需要用到IOC容器当中的B对象,而B对象同样需要IOC中的A对象,因此报错。但是如果是通过Setter方法注入,就不会产生该问题
原因如下:

在第一步和第二内部,是首先创建实例化对象,然后进行属性注入,如果采用构造方法注入,A、B两个对象都无法成功实例化,但是如果使用的是setter方法,就可以在实例化之后进行属性注入,就不会有问题。如果构造方法和setter方法同时存在,会优先使用构造方法,因此会爆错。

解决方法

1. 懒加载

在其中一个需要加载的地方加上 @Lazy 注解,就可以延迟加载该属性。

@Component
@Component
class A{

    private B b;

    @Lazy
    public A(B b) {
        this.b = b;
    }
}

如上,在private B b;加上 @Lazy ,再初始化A对象的时候,就不会直接注入a,不用去IOC容器当中查找单例对象A,就不会产生循环依赖问题。创建B的时候依然会使用A的构造方法,但是由于使用@Lazy注解,因此Spring会生成一个代理对象返回,使得A可以正常创建。从而解决以来循环问题。
使用@Lazy解决循环依赖问题

2. 二级缓存

在缓存池当中缓存A和B对象,其中属性都是空白的。


缺点:

如果注入的是被AOP管理的对象,那么就需要在单例池当中注入被动态代理的对象,而不是原对象。

怎么办?

三级缓存

三级缓存:如果引用的对象配置了AOP,那在单例池中最终就会需要注入动态代理对象,而不是原对象。而生成动态代理是要在对象初始化完成之后才开始的。于是Spring增加三级缓存,保存所有对象的动态代理配置信息。在发现有循环依赖时,将这个对象的动态代理信息获取出来,提前进行AOP,生成动态代理。

Spring 是如何解决循环依赖问题的

/*一级缓存,理解为:已经实例化,属性填充,初始化完成的 bean*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
 
/*三级缓存*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
/*二级缓存,理解为:实例化完成但属性填充未完成的 bean*/
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

 @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //从一级缓存,也就是单例池当中获取数据
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        //一级缓存没有就从二级缓存拆线呢
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized(this.singletonObjects) {
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                        //二级也没有,说明是需要AOP,因此会从三级缓存加载singletonFactory ,这里面保存的的代理信息,通过代理信息,在二级缓存当中生成代理对象,从而可以解决依赖循环。                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }

注意:

如果不是单例模式,就不会出现循环引用的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值