解决Spring循环依赖问题

428 阅读1分钟

简单说下依赖问题

  1. Spring团队提倡使用基于构造方法的注入,在上个项目中我也把 @Autowired 注解大部分都换成了构造器。当然如果没有使用 lombok 的 RequiredArgsConstructor 注解,自己手动往构造器里添加参数确实挺麻烦。
  2. 在遇到循环依赖问题的时候,可以在出现循环的类上继承 SmartInitializingSingleton 接口,该接口的 afterSingletonsInstantiated 方法会在所有 bean 创建完后才会调用。

使用 SmartInitializingSingleton 解决循环依赖

public class ClassA {
  private ClassB classB;
  
  public ClassA(ClassB classB) {
    this.classB = classB;
  }
  ...
}

// 因为 ClassA 和 ClassB 互相依赖,所以 ClassB 在构造器里不注入 ClassA,
// 而是借助 SmartInitializingSingleton 的特性,在 ClassA 创建完后再注入到 ClassB 中。
public class ClassB implements SmartInitializingSingleton, 
  ApplicationContextAware {
  private ClassA classA;
  private static ApplicationContext applicationContext;
 
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) 
    throws BeansException {
        ClassB.applicationContext = applicationContext;
  }
  
  @Override
  public void afterSingletonsInstantiated() {
    classA = applicationContext.getBean(classA.class);
  }
  ...
}

上面使用 SmartInitializingSingleton 的代码是很啰嗦,比如可以通过自定义接口的方式减少冗余代码。

public interface InitSupport {

    void init(ApplicationContext applicationContext);
}

public Class ClassB implements InitSupport {
  private ClassA classA;

  @Override
  public void init(ApplicationContext applicationContext) {
    classA = applicationContext.getBean(classA.class);
  }
  ...
}

// 获取所有继承 InitSupport 的 bean,然后调用这些 bean 的 init 方法初始。
// 还可以进一步新建个 RuntimeProvider 这样的接口类代替 ApplicationContext 去获取 bean 。
public Class InitSupportScanBean implements SmartInitializingSingleton,
  ApplicationContextAware {
  private static ApplicationContext applicationContext;
 
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) 
    throws BeansException {
        InitSupportScanBean.applicationContext = applicationContext;
  }
  
  @Override
  public void afterSingletonsInstantiated() {
    Map<String, InitSupport> beanMap = applicationContext
      .getBeansOfType(InitSupport.class);

    // 调用初始方法
    beanMap.forEach((beanName, initSupport) -> {
      initSupport.init(applicationContext);
    });
  }
}