前言:在最近的工作当中偶然遇到了Spring的循环依赖的Bug,搜集了网上的资料参考和分析,写了一份总结进行学习和分享!

目录

一、错误概述

二、根本原因

三、解决方案

四、Spring是如何解决循环依赖的


一、错误概述

在最近的工作当中,启动项目的时候报了一个类似于以下的错误:

Description:

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

┌─────┐

|  AService (field private com.example.demo.service.BService

com.example.demo.service.AService.bService)

↑     ↓

|  BService (field private com.example.demo.service.AService

com.example.demo.service.BService.aService)

└─────┘

二、根本原因

在AService 注入了BService这个Bean,在BService 同时也注入了 AService 这个Bean,两个类相互引用对方,导致Spring在初始化bean的时候不知道先初始化哪 个,从而形成循环依赖注入。详细如下:

AService:

package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AService {
    @Autowired
    private BService bService;
}

BService:

package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BService {
    @Autowired
    private AService aService;
}

三、解决方案

1.从根本上解决这个问题,不要让它们相互依赖,重新设计代码结构,代码解耦肯定是最优解!

2.添加@Lazy注解

package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AService {
    @Lazy
    @Autowired
    private BService bService;
}
package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BService {
    @Lazy
    @Autowired
    private AService aService;
}

这个注解的原理是:当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,则不会直接创建所依赖的对象,而是使用动态代理创建一个代理类 通俗的来说:AService的创建,需要依赖BService,那就不直接创建BService了,而是使用动态代理创建了一个代理类BService1,此时AService和BService就不是相互依赖了,从而变成了AService依赖了一 个代理类BService1,BService依赖AService。但因为在注入依赖时,AService并没有完全初始化结束, 实际上注入了一个代理对象,只有他当首次被使用的时候才会被完全初始化。

3.在application.properties配置当中加入

spring.main.allow-circular-references=true

注:从SpringBoot 2.6开始默认禁用了循环依赖

四、Spring是如何解决循环依赖的

单例Bean的初始化需要经历三步:

springboot 出现循环依赖错误 spring 循环依赖引发问题_初始化

Bean初始化步骤:

注入就发生在第二步,属性赋值,结合这个过程,Spring通过三级缓存解决了循环依赖:

一级缓存 : Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例。

二级缓存 : Map<String,Object> earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean实例。

三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象工厂,用于保存 bean 创 建工厂,以便于后面扩展有机会。

springboot 出现循环依赖错误 spring 循环依赖引发问题_实例化_02

当 A、B 两个类发生循环依赖时:

springboot 出现循环依赖错误 spring 循环依赖引发问题_实例化_03

 

A实例的初始化过程:

1.创建A实例,实例化的时候把A对象工厂放入三级缓存,表示A开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道。

springboot 出现循环依赖错误 spring 循环依赖引发问题_java_04

2.A注入属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B。

3.同样,B注入属性时发现依赖A,它就会从缓存里找A对象。依次从一级到三级缓存查询A,从三级缓存通过对象工厂拿到A,发现A虽然不太完善,但是存在,把A放入二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入一级缓存。

4.接着A继续属性赋值,顺利从一级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入一级缓存。