1

本周项目中遇到了如下报错
图片.png
报错说的很明白,buildingService中用到了divisionalService,divisionalservice中也用到了buildingService。在执行构造函数时就出现了问题。

 @Autowired
  public BuildingServiceImpl(BuildingRepository buildingRepository,
                             DivisionalWorkService divisionalWorkService
  ) {
    this.buildingRepository = buildingRepository;
    this.divisionalWorkService = divisionalWorkService;
  }

我们想要构造BuildingService就需要先构造一个divisionalWorkService,但是想要构造divisionalWorkService又得构造一个BuildingService
很显然结果就是会卡住,对应的解决方法也很简单,就是先让其中一方在不需要另一方的情况下构造出来其中一个,再去构造另外一个。
具体做法如下:

@Autowired
  public DivisionalWorkServiceImpl(DivisionalWorkRepository divisionalWorkRepository,
                                   @Lazy BuildingService buildingService) {
    this.divisionalWorkRepository = divisionalWorkRepository;
    this.buildingService = buildingService;
  }

在其中一方的的构造函数中添加@Lazy注解。
下面是我找到的解释:
A simple way to break the cycle is by telling Spring to initialize one of the beans lazily. So, instead of fully initializing the bean, it will create a proxy to inject it into the other bean. The injected bean will only be fully created when it’s first needed.

我们可以告诉spring来懒初始化其中一个bean,而不是完全初始化这个bean,spring会根据所需bean创建一个代理,通过此代理来创建所需bean。当所需bean创建完并且用到另一个bean时再去完全初始化另一个bean,此时有一方已被完全创建好可以直接创建。

大致流程:
未命名文件(9).png

除了此方法spring还提供了其他很多方法来避免循环依赖,其中Spring官方文档建议使用的并不是上面的处理方法,而是使用 setter 注入。
也就是说并不是在构造函数中注入,这样Spring会创建bean,但是只有当我们用到它们时才会注入依赖项。

为此单独进行了一下测试:

@Service
public class SeverA {
  private SeverB severB;

  String message = "I an sever A, B used";

  @Autowired
  public void setSeverB(SeverB severB) {
    System.out.println("A中注入B");
    this.severB = severB;
  }

  public void useB() {
    System.out.println(severB.message);
  }
}
@Service
public class SeverB {
  private SeverA severA;

  String message = "I an sever B, A used";

  @Autowired
  public void setSeverA(SeverA severA) {
    System.out.println("B中注入A");
    this.severA = severA;
  }

  public void useA() {
    System.out.println(severA.message);
  }
}

但是这样的话又会触发另一种循环依赖的报错
图片.png
不鼓励依赖循环引用,默认情况下禁止使用循环引用,更新应用程序以消除bean之间的依赖循环。作为最后手段,可以通过将spring.main.allow-circular-references设置为true来自动中断循环。

也就是说我们需要这样配置:
图片.png
之后又尝试在项目启动时调用serverA使用severB。
图片.png
结果如下
图片.png

再尝试把severA.useB()去掉,发现仍然进行了注入操作
图片.png
这与我们的想像并不相符合。
下面是官方文档对此的解释:

图片.png
也就是说官方文档并没有说明setter函数是在相应服务被使用的情况下再去调用,原理可能和上一个方法相同,也是不完全注入。但是官方文档并不推荐此种做法,所以我们还是尽量要避免循环依赖的产生。


李明
441 声望18 粉丝