基于 Spring Boot 构造器注入循环依赖及解决方式

 2022-09-15
原文地址:https://cloud.tencent.com/developer/article/1738543

1. 循环依赖是什么?

Bean A 依赖 B,Bean B 依赖 A这种情况下出现循环依赖。

Bean A → Bean B → Bean A

更复杂的间接依赖造成的循环依赖如下。

Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

2. 循环依赖会产生什么结果?

当Spring正在加载所有Bean时,Spring尝试以能正常创建Bean的顺序去创建Bean。

例如,有如下依赖:

Bean A → Bean B → Bean C

Spring先创建beanC,接着创建bean B(将C注入B中),最后创建bean A(将B注入A中)。

但当存在循环依赖时,Spring将无法决定先创建哪个bean。这种情况下,Spring将产生异常BeanCurrentlyInCreationException。

当使用构造器注入时经常会发生循环依赖问题。如果使用其它类型的注入方式能够避免这种问题。

3. 构造器注入循环依赖实例

首先定义两个相互通过构造器注入依赖的bean。

    @Component
    public class CircularDependencyA {
     
     private CircularDependencyB circB;
     
     @Autowired
     public CircularDependencyA(CircularDependencyB circB) {
      this.circB = circB;
     }
    }
    @Component
    public class CircularDependencyB {
     
     private CircularDependencyA circA;
     
     @Autowired
     public CircularDependencyB(CircularDependencyA circA) {
      this.circA = circA;
     }
    }
    @Configuration
    @ComponentScan(basePackages = { "com.baeldung.circulardependency" })
    public class TestConfig {
    }
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { TestConfig.class })
    public class CircularDependencyTest {
     
     @Test
     public void givenCircularDependency_whenConstructorInjection_thenItFails() {
      // Empty test; we just want the context to load
     }
    }

运行方法givenCircularDependency_whenConstructorInjection_thenItFails将会产生异常:

BeanCurrentlyInCreationException: Error creating bean with name ‘circularDependencyA’: Requested bean is currently in creation: Is there an unresolvable circular reference?

4.解决方法

处理这种问题目前有如下几种常见方式。

4.1 重新设计

重新设计结构,消除循环依赖。

4.2 使用注解 @Lazy

一种最简单的消除循环依赖的方式是通过延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入。

    @Component
    public class CircularDependencyA {
     
     private CircularDependencyB circB;
     
     @Autowired
     public CircularDependencyA(@Lazy CircularDependencyB circB) {
      this.circB = circB;
     }
    }

使用@Lazy后,运行代码,可以看到异常消除。

4.3 使用Setter/Field注入

Spring文档建议的一种方式是使用setter注入。当依赖最终被使用时才进行注入。对前文的样例代码少做修改,来观察测试效果。

    @Component
    public class CircularDependencyA {
     
     private CircularDependencyB circB;
     
     @Autowired
     public void setCircB(CircularDependencyB circB) {
      this.circB = circB;
     }
     
     public CircularDependencyB getCircB() {
      return circB;
     }
    }
    @Component
    public class CircularDependencyB {
     
     private CircularDependencyA circA;
     
     private String message = "Hi!";
     
     @Autowired
     public void setCircA(CircularDependencyA circA) {
      this.circA = circA;
     }
     
     public String getMessage() {
      return message;
     }
    }
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { TestConfig.class })
    public class CircularDependencyTest {
     
     @Autowired
     ApplicationContext context;
     
     @Bean
     public CircularDependencyA getCircularDependencyA() {
      return new CircularDependencyA();
     }
     
     @Bean
     public CircularDependencyB getCircularDependencyB() {
      return new CircularDependencyB();
     }
     
     @Test
     public void givenCircularDependency_whenSetterInjection_thenItWorks() {
      CircularDependencyA circA = context.getBean(CircularDependencyA.class);
    
      Assert.assertEquals("Hi!", circA.getCircB().getMessage());
     }
    }

4.4 使用@PostConstruct

    @Component
    public class CircularDependencyA {
     
     @Autowired
     private CircularDependencyB circB;
     
     @PostConstruct
     public void init() {
      circB.setCircA(this);
     }
     
     public CircularDependencyB getCircB() {
      return circB;
     }
    }
    @Component
    public class CircularDependencyB {
     
     private CircularDependencyA circA;
      
     private String message = "Hi!";
     
     public void setCircA(CircularDependencyA circA) {
      this.circA = circA;
     }
      
     public String getMessage() {
      return message;
     }

4.5 实现ApplicationContextAware与InitializingBean

    @Component
    public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
     
     private CircularDependencyB circB;
     
     private ApplicationContext context;
     
     public CircularDependencyB getCircB() {
      return circB;
     }
     
     @Override
     public void afterPropertiesSet() throws Exception {
      circB = context.getBean(CircularDependencyB.class);
     }
     
     @Override
     public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
      context = ctx;
     }
    }
    @Component
    public class CircularDependencyB {
     
     private CircularDependencyA circA;
     
     private String message = "Hi!";
     
     @Autowired
     public void setCircA(CircularDependencyA circA) {
      this.circA = circA;
     }
     
     public String getMessage() {
      return message;
     }
    }

5.总结

处理循环依赖有多种方式。首先考虑是否能够通过重新设计依赖来避免循环依赖。如果确实需要循环依赖,那么可以通过前文提到的方式来处理。优先建议使用setter注入来解决。

以上这篇基于SpringBoot构造器注入循环依赖及解决方式就是小编分享给大家的全部内容了,希望能给大家一个参考。