Spring如何解决循环注入问题

33 阅读4分钟

1.什么是循环注入问题

IOC(控制反转)和AOP(面向切面编程)是Spring的两大核心特性,而循环注入是指在控制反转即将对象的控制权交给Spring后,由于A的创建需要B,而B的创建需要A的一个循环,这就是注入循环。而循环依赖的这一问题,Spring已经有了对应的解决方案,现在Spring 默认支持单例 Bean 的循环依赖(通过三级缓存机制),但是不能解决通过构造参数注入引发的循环依赖。这里先提前介绍一下Spring三个缓存的作用:

  1. 一级缓存(singletonObjects):存储完全初始化完成的单例 Bean(可以直接使用的成品 Bean)。
  2. 二级缓存(earlySingletonObjects:存储提前暴露的单例 Bean(实例化完成,但未完成属性注入和初始化的半成品 Bean)。
  3. 三级缓存(singletonFactories):存储Bean 的工厂对象ObjectFactory),用于在需要时生成提前暴露的 Bean(半成品)。

2.Spring如何解决循环注入问题

Spring通过三级缓存办法来解决单例Bean循环依赖问题。

现假设AService中注入了BService,而在BService中也需要注入AService,这就导致了循环注入的问题。

此时,假设我们先创建了AService,在初始化AService的时候,会向Spring的三级缓存添加一个存储Bean的对应工厂,然后我们需要注入BService,由于没有BService实例,我们又开始创建BService实例,在初始化BService实例时,发现需要AService。

此时Spring会去三层缓存中从1,2,3的顺序依次寻找AService实例,找到三级缓存时会发现有一个AService的对应工厂,他会生成一个AService的半成品,提前暴露Bean的引用,提供AService实例的地址,生成的AService半成品此时被放入到二级缓存中,并移除三级缓存中的A的工厂,避免重复生成。

有了AService的半成品实例,BService初始化完成,存入一级缓存,此时又回到AService的初始化,BService已经完成初始化了,可以注入AService中,AService的初始化也随之完成,清除二层缓存中的AService将其存入一级缓存中,AServic和BService的初始化都被解决。

熟悉数学的朋友或可以发现这样的解决办法非常像我们在解决二元一次方程时将其中一个变量先看成常数的解法,x与y相关却又都无法单独解出,就像这里的A和B循环注入,无法自我生成,此时Spring的解决办法是先创建A的一个半成品,去支持B先完成实例化的思想,对应了解方程时将x看作常量先去得到y的表达式的思想。

image.png

3.Spring循环注入的开发中的发生情况

事务失效是Spring循环注入经常发生的场景,由于事务的复杂性导致在同一业务层中一个业务中引用了同一业务层的其他方法,由于事务管理的注解@Transactional是基于Spring的AOP来开发的,但是一个方法能被代理的前提是他被直接的声明在了接口之中,而同一事务层中调用其他方法已经不在是接口中的方法而是业务层实现类中的方法了,此时事务失效,导致业务与预期效果产生偏差,此时我就需要在业务层中注入被本身实现的接口,再用接口中的方法去替代导致事务失效的方法,使用接口声明的方法让事务重新生效。此时循环注入的问题也就发生了。

但是在我们开发的过程中,其实循环注入的方法是不被提倡的,能避免就尽量避免。 如何避免呢,方法如下:

  1. 对于Service层,其下还有一个Mapper层,我们不能Service循环注入,那可以将Mapper来替代其中的Service。如AService注入BService,BService不再注入AService转而注入AMapper,这样循环就不再发生。 2. 拆分Service:当我们发现AService中用到BService里的方法后会产生循环注入,我们可以选择拆分BService,将那个方法单独写在CService中,这样循环注入的情况也不会发生。