Spring 如何解决循环依赖

1 Bean的生命周期

1.1 Spring Bean 的生命周期

具体看这篇博客–>> Spring Bean 的生命周期

1.2 Bean 的生成步骤


被 Spring 管理的对象叫做 Bean 。Bean的生成步骤如下:

1、Spring 扫描 class 得到 BeanDefinition;
2、根据得到的 BeanDefinition 去生成 bean;
3、首先根据 class 推断构造方法;
4、根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象);
5、填充原始对象中的属性(依赖注入);
6、如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象;
7、把最终生成的代理对象放入单例池(源码中叫做 singletonObjects)中,下次 getBean 时就直接从单例池拿即可;
8、对于 Spring 中的 Bean 的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很多很多,这里不详细说了。

我们可以发现,在得到一个原始对象后,Spring 需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?

比如上文说的 A 类,A 类中存在一个 B 类的 b 属性,所以,当 A 类生成了一个原始对象之后,就会去给 b 属性去赋值,此时就会根据 b 属性的类型和属性名去 BeanFactory 中去获取 B 类所对应的单例bean。

1. 如果此时 BeanFactory 中存在 B 对应的 Bean,那么直接拿来赋值给 b 属性;
2. 如果此时 BeanFactory 中不存在 B 对应的 Bean,则需要生成一个 B 对应的 Bean,然后赋值给 b属性。

问题就出现在「第二种」情况,如果此时 B 类在 BeanFactory 中还没有生成对应的 Bean,那么就需要去生成,就会经过 B 的 Bean 的生命周期。

那么在创建 B 类的 Bean 的过程中,如果 B 类中存在一个 A 类的 a 属性,那么在创建 B 的 Bean 的过程中就需要 A 类对应的 Bean,但是,触发 B 类 Bean 的创建的条件是 A 类 Bean 在创建过程中的依赖注入,所以这里就出现了循环依赖:

A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)

从而导致 A Bean 创建不出来,B Bean 也创建不出来。

这是循环依赖的场景,但是上文说了,在 Spring 中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是「三级缓存」。

1.3 三级缓存

一级缓存为:singletonObjects;
二级缓存为:earlySingletonObjects;
三级缓存为:singletonFactories;

/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);

/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);

/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);

singletonObjects」中缓存的是已经经历了完整生命周期的bean对象。

「earlySingletonObjects」比 singletonObjects 多了一个 early ,表示缓存的是早期的 bean对象。早期指的是 Bean 的生命周期还没走完就把这个 Bean 放入了 earlySingletonObjects。

「singletonFactories」中缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象的。

refSpring中的循环依赖_spring循环依赖_SuZhan7710的博客-CSDN博客

2 Spring中的循环依赖


2.1 什么是循环依赖


循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。

我们这里以两个类A和B为例进行讲解,如下是A和B的声明:

@Component
public class A {
  private B b;
  public void setB(B b) {
    this.b = b;
  }
}
@Component
public class B {
  private A a;
  public void setA(A a) {
    this.a = a;
  }
}


结论先行:

1.构造器循环依赖----初始化失败

2.field属性注入并且是单列模式循环依赖(setter方式)----初始化成功

3.field属性注入循环依赖(prototype)----初始化失败

现象总结:同样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。因为@Service默认是单例的,所以单例的属性注入是可以成功的。

2.2 Spring 如何解决循环依赖


Spring bean注入流程
类实例化 -> 属性注入 -> 执行初始化方法 -> (如果有需要)生成代理对象 -> 使用

三级缓存解决循环依赖流程:

A、B两个类相互依赖,初始化A的时候,第一步实例化A完成(生成对象工厂实例放入三级缓存),注入依赖属性B,一级缓存查询B没有,二级缓存查询B没有,

初始化B(生成对象工厂实例放入三级缓存),注入依赖属性A,一级缓存查询A没有,二级缓存查询A没有,三级缓存查询到A的对象工厂,需要AOP增强则生成A的代理对象,没有则直接创建A实例对象,并将A放入到二级缓存,注入A的代理对象完成,生成代理对象B,B移入一级缓存。

继续A属性注入(B的代理对象),然后可能还会依赖注入C对象流程和B一致,所有依赖注入完成后A初始化,生成A的代理对象,发现A的代理对象已存在,则跳过,放入一级缓存。此时A的代理对象也是提前生成的,但是仅针对循环依赖提前生成。

下面为流程图:

Spring 如何解决循环依赖_第1张图片

三级缓存不存在循环依赖的正常生成流程:

第一步实例化A完成(生成对象工厂实例放入三级缓存,此时虽然放入三级缓存,但没有生成代理对象),注入属性,执行初始化方法,生成代理对象,移入一级缓存。

ref spring为什么不能只用一二级缓存来解决循环依赖? - sahara-随笔 - 博客园

2.3 spring中出现循环依赖的场景


spring中出现循环依赖主要有以下场景:

Spring 如何解决循环依赖_第2张图片

2.4 循环依赖常见的解决方案


1.重新设计

当有一个循环依赖,很可能是有一个设计问题并且各责任没有得到很好的分离。应该尽量正确地重新设计组件,以便它们的层次是精心设计的,也没有必要循环依赖。

如果不能重新设计组件(可能有很多的原因:遗留代码,已经被测试并不能修改代码,没有足够的时间或资源来完全重新设计…),但有一些变通方法来解决这个问题,如下面几种方式

2.延迟加载@Lazy注解:

  • 原理是发现有@lazy注解的依赖为其生成代理类,依赖代理类,从而实现了解耦

解决Spring 循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。

@Service
public class ServiceAImpl implements ServiceA {
    private ServiceB serviceB;
 
    public ServiceAImpl(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}


3.Setter/Field 注入
简单地说,你对你需要注入的bean是使用setter注入(或字段注入),而不是构造函数注入。

@Service
public class ServiceAImpl implements ServiceA {
    private ServiceB serviceB;
 
    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}


3.spring 三级缓存实现原理


3.1 一级缓存

已经完成了bean的所有流程,即实例化、注入、初始化完成的bean实例,并放进了单列池中了。

一级缓存:
/** 保存所有的singletonBean的实例,已经完成了所有流程可以用的bean */
private final Map singletonObjects = new ConcurrentHashMap(64);


3.2 二级缓存


保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入,但已经提前完成了AOP增强。

/** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
private final Map earlySingletonObjects = new HashMap(16);


为什么需要二级缓存

如果出现循环依赖+aop时,多个地方注入这个动态代理对象需要保证都是同一个对象,而三级缓存中的取出来的动态代理对象每次都是新对象,地址值不一样。

3.3三级缓存


singletonBean的生产工厂,即创建单列bean的工厂。

存的是一个函数接口, 函数接口实现 创建动态代理调用BeanPostProcessor 。 为了避免重复创建, 调用把返回的动态代理对象或者原实例存储在二级缓存中。

/** singletonBean的生产工厂,即创建单列bean的工厂*/
private final Map> singletonFactories = new HashMap>(16);


在取三级缓存并创建bean时,ObjectFactory调用getObject(),不仅会取到bean实例而且还会判断是否生成代理对象,并放到二级缓存中。

为什么需要第三级缓存?

解决如果出现循环依赖,判断是否需要提前进行aop等操作

一级缓存:
/** 保存所有的singletonBean的实例,已经完成了所有流程可以用的bean */
private final Map singletonObjects = new ConcurrentHashMap(64);
 
二级缓存:
 
/** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
private final Map earlySingletonObjects = new HashMap(16);
 
三级缓存:
/** singletonBean的生产工厂,即创建单列bean的工厂*/
private final Map> singletonFactories = new HashMap>(16);
 
/** 保存所有已经完成初始化的Bean的名字(name) */
private final Set registeredSingletons = new LinkedHashSet(64);
 
/** 
标识指定name的Bean对象是否处于创建状态,这个状态非常重要。
如果创建完毕之后,会将其从 singletonsCurrentlyInCreation 列表中移除,
并且会将创建好的 bean 放到另外一个单例列表中,这个列表叫做 singletonObjects即一级缓存中。
*/
private final Set singletonsCurrentlyInCreation =
    Collections.newSetFromMap(new ConcurrentHashMap(16));

ref Spring 循环依赖解决方案_spring如何解决循环依赖-CSDN博客
 

你可能感兴趣的:(Spring,spring,java,后端)