目录
项目场景:
后台架构为SpringBoot,业务上有一套调第三方接口逻辑,由于不稳定,需要另写一套新的。旧的有部分接口要依赖新的逻辑,新的有一部分需要通过旧的去获取第三方数据。于是,导致了代码上这两个类互相依赖,一启动直接裂开,报循环依赖错误。
后来查阅了资料,了解了循环依赖注入的原理,在不解耦的情况下(当然最好的方式是解耦,重新设计),也有了几种解决方案,最后空闲之余,写了这篇博客来总结下这次有意思的体验。
问题描述:
问题出现很明显,两个类互相依赖注入,这种情况下Spring不知道先创建哪个bean,
就会抛出BeanCurrentlyInCreationException错误,如下
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:219)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1304)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1224)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:884)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788)
... 57 more
原因分析:
首先说一下什么是循环依赖注入,简单举个例子,比如有两个类,ClassA 和 ClassB。其中,ClassA 中通过依赖注入了 ClassB,ClassB中也通过依赖注入了 ClassA,此时ClassA和ClassB互相依赖,成为闭环,就会导致异常产生。
但是其实Spring是可以有策略可以解决循环依赖的,不过如果是通过构造器注入的方式,Spring无法解决。如果是setter方法注入则可以解决此问题,后面再说。这里先写个小demo来重现这个问题。如下
ClassA
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 测试循环依赖问题.
*
* A类
*
* @author linzp
* @version 1.0.0
* CreateDate 2020/10/12 19:15
*/
@Service
public class ClassA {
/**
* 依赖classB.
*/
private ClassB classB;
@Autowired
public ClassA(ClassB classB){
this.classB = classB;
}
public void test(){
}
}
ClassB
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 测试循环依赖问题.
*
* B类
*
* @author linzp
* @version 1.0.0
* CreateDate 2020/10/12 19:18
*/
@Service
public class ClassB {
/**
* 依赖classA.
*/
private ClassA classA;
@Autowired
public ClassB(ClassA classA){
this.classA = classA;
}
}
如果在这时启动项目,就会启动失败,结果如下图。
如果编写单元测试来调用ClassA的方法的话,则会抛如上的 BeanCurrentlyInCreationException 异常。
注:这里两个类都是基于构造器注入的。
解决方案:
解决的方案有很多种。但是,如果在可能的情况下,重新设计和解耦 是推荐的办法。如果不方便重构代码,也可以使用其他方式来解决。
一、改为使用 Setter 方式注入
这是Spring官方推荐的做法。通过使用构造器注入可以解决问题。更改后的两个类代码如下:
@Service
public class ClassA {
/**
* 依赖classB.
*/
private ClassB classB;
/**
* 使用Setter方式注入ClassB
* @param classB
*/
@Autowired
public void setClassB(ClassB classB){
this.classB = classB;
}
public void test(){
}
}
@Service
public class ClassB {
/**
* 依赖classA.
*/
private ClassA classA;
/**
* 使用Setter方式注入ClassA
* @param classA
*/
@Autowired
public void setClassA(ClassA classA){
this.classA = classA;
}
}
二、使用 @Lazy 注解
通过在其中一个或两个类的注入属性或方法中,添加 @Lazy注解,一般亦可解决问题
@Service
public class ClassA {
/**
* 依赖classB.
*/
private ClassB classB;
/**
* 使用构造器方式注入ClassB
* @param classB
*/
@Autowired
@Lazy //添加@Lazy注解
public ClassA(ClassB classB){
this.classB = classB;
}
public void test(){ }
}
@Service
public class ClassB {
/**
* 依赖classA.
*/
private ClassA classA;
/**
* 使用构造器方式注入ClassA
* @param classA
*/
@Autowired
@Lazy //添加@Lazy注解
public ClassB(ClassA classA){
this.classA = classA;
}
}
三、使用 PostConstruct 注解
@Service
public class ClassA {
/**
* 依赖classB.
*/
private ClassB classB;
/**
* 使用构造器方式注入ClassB
* @param classB
*/
@Autowired
public ClassA(ClassB classB){
this.classB = classB;
}
@PostConstruct
public void init(){
classB.setClassA(this);
}
public void test(){ }
}
其中,ClassB中的ClassA属性,由set方法来设置,这时候可以看到在ClassB中并没有使用注入的方式。
@Service
public class ClassB {
/**
* 依赖classA.
*/
private ClassA classA;
public void setClassA(ClassA classA){}
}