美文网首页Java 核心技术Java
Spring中的循环依赖

Spring中的循环依赖

作者: cbw100 | 来源:发表于2020-03-27 10:18 被阅读0次

1. 什么是循环依赖

当一个bean A依赖于另一个bean B,并且bean B也依赖于bean A时,就会发生这种情况:

Bean A → Bean B → Bean A

当然,我们能有更多的beans依赖如下:

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

2. Spring中发生了什么

当Spring上下文加载所有bean时,它将尝试按照它们完全工作所需的顺序创建bean。 例如,如果我们没有循环依赖关系,例如以下情况:

Bean A→Bean B→Bean C

Spring将创建bean C,然后创建bean B(并将bean C注入到其中),然后创建bean A(并将bean B注入到其中)。

但是,当具有循环依赖关系时,Spring无法决定应首先创建哪个bean,因为它们相互依赖。 在这些情况下,Spring在加载上下文时将引发BeanCurrentlyInCreationException。

使用构造函数注入时,它可能会在Spring中发生。 如果使用其他类型的注入,则应该不会发现此问题,因为依赖项将在需要时注入,而不是在上下文加载时注入。

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类,将其称为TestConfig,它指定要扫描组件的基本程序包。 假设我们的bean是在“ com.baeldung.circulardependency”包中定义的:

@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}

最后,我们可以编写一个JUnit测试来检查循环依赖。 该测试可以为空,因为在上下文加载期间将检测到循环依赖关系。

@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
    }
}

If you try to run this test, you will get the following exception:

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

打破周期的一种简单方法是说Spring懒惰地初始化一个bean。 也就是说:与其完全初始化bean,不如创建一个代理以将其注入到另一个bean中。 注入的Bean仅在首次使用时才完全创建。

要通过我们的代码尝试此操作,可以将CircularDependencyA更改为以下内容:

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

如果现在运行测试,您将看到这次不会发生错误。

4.3 使用 Setter/Field 注入

最受欢迎的解决方法之一,也是Spring文档提出的建议,就是使用setter注入。

简而言之,如果您更改了使用setter注入(或字段注入)而不是构造函数注入的方式连接bean的方式,那么确实可以解决该问题。 通过这种方式,Spring创建了bean,但是直到需要它们时才注入依赖项。

我们来做–让我们更改类以使用setter注入,并将另一个字段(消息)添加到CircularDependencyB中,以便我们进行适当的单元测试:

@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());
    }
}

以下说明了上面看到的注释:

@Bean:告诉Spring框架,必须使用这些方法来检索要注入的bean的实现。

@Test:测试将从上下文中获取CircularDependencyA bean,并断言其CircularDependencyB已被正确注入,并检查其消息属性的值。

4.4 使用 @PostConstruct

打破周期的另一种方法是在一个bean上使用@Autowired注入依赖项,然后使用带有@PostConstruct注释的方法设置另一个依赖项。

我们的bean可能具有以下代码:

@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 and InitializingBean

如果其中一个bean实现了ApplicationContextAware,则该bean可以访问Spring上下文,并且可以从那里提取另一个bean。 在实现InitializingBean时,我们指示该bean必须在其所有属性都已设置之后才能执行一些操作; 在这种情况下,我们要手动设置依赖关系。

我们的bean的代码为:

@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. 总结

在Spring中,有很多方法可以处理循环依赖。 首先要考虑的是重新设计bean,因此不需要循环依赖:它们通常是可以改进的设计的症状。

但是,如果您的项目中绝对需要循环依赖,则可以遵循此处建议的一些解决方法。

首选方法是使用二传手进样。 但是还有其他选择,通常基于阻止Spring管理Bean的初始化和注入,然后自己使用一种或多种策略来完成。

这些示例可以在GitHub项目中找到。

相关文章可看:Java详解之Spring Bean的循环依赖解决方案

相关文章

  • Spring 是如何解决循环依赖的?

    Spring 是如何解决循环依赖的? 循环依赖: Spring 循环依赖有三种情况: 构造器的循环依赖,这种依赖 ...

  • Spring-IOC-循环依赖检测与Bean的创建

    Spring容器的循环依赖检测 Spring容器循环依赖包括:构造器循环依赖和setter循环依赖。 1- 构造器...

  • Spring之循环依赖及解决方式

    1.Spring循环依赖 循环依赖指Spring对象之间的循环引用,最终形成死循环。举例: A依赖于B,B依赖于C...

  • spring循环依赖

    Spring 循环依赖 什么是循环依赖 概念: 在代码中对象A依赖对象B,对象B依赖对象A,对象相互之间依赖关系...

  • Spring核心之bean生命周期和三级缓存

    在使用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring已经帮我们去解决循环依赖问题,对我...

  • Spring中的循环依赖

    什么是循环依赖 循环依赖就是循环引用,在spring中,就是两个或者多个bean相互之间持有对方。如下图,Clas...

  • Spring中的循环依赖

    1. 什么是循环依赖 当一个bean A依赖于另一个bean B,并且bean B也依赖于bean A时,就会发生...

  • Spring中的循环依赖

    循环依赖   Spring使用依赖注入(DI)来实现控制反转(IoC),因而不可避免的会存在循环依赖的情况:当容器...

  • Spring中的循环依赖

    首先看一下出问题的代码片段: 这两段代码中定义了两个Service类:TestService1和TestServi...

  • Spring循环依赖问题

    什么是循环依赖 循环依赖就是两个对象之间存在相互依赖的关系; Spring是如何解决循环依赖的 Spring使用三...

网友评论

    本文标题:Spring中的循环依赖

    本文链接:https://www.haomeiwen.com/subject/bkakuhtx.html