什么是轮回依赖以及处理方式版权声明

原创
小哥 3年前 (2022-11-11) 阅读数 47 #大杂烩

1.什么是循环依赖?

它发生在bean A依赖他人bean B时,bean B依赖于bean A:

豆A→豆B→豆A

当然,我们可以有更多的提示:

豆A→豆B→豆C→豆D→豆E→豆A

2.春天会发生什么

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

豆A→豆B→豆C.

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

然而当存在循环依赖时,Spring无法决定首先创建哪一个。bean因为他们相互依赖。在这些情况下,Spring将在加载上下文时引发 BeanCurrentlyInCreationException

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

3.一个简单的例子

让我们定义两个相互依存的bean(通过构造器注入):

1

2

3

4

5

6

7

8

9

10

@Component

public class CircularDependencyA {

private CircularDependencyB circB;

@Autowired

public CircularDependencyA(CircularDependencyB circB) {

this .circB = circB;

}

}

1

2

3

4

5

6

7

8

9

10

@Component

public class CircularDependencyB {

private CircularDependencyA circA;

@Autowired

public CircularDependencyB(CircularDependencyA circA) {

this .circA = circA;

}

}

现在我们可以为测试编写一个。Configuration同学们,就这么说吧 TestConfig ,指定要扫描的组件的基础包。假设我们的bean在包“ com.baeldung.circulardependency ” 中定义:

1

2

3

4

@Configuration

@ComponentScan (basePackages = { "com.baeldung.circulardependency" })

public class TestConfig {

}

最后,我们可以编写JUnit测试以检查循环依赖项。测试可能为空,因为在上下文加载期间检测到循环依赖项。

1

2

3

4

5

6

7

8

9

@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

}

}

如果尝试运行此测试,将出现以下异常:

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更改:

1

2

3

4

5

6

7

8

9

10

@Component

public class CircularDependencyA {

private CircularDependencyB circB;

@Autowired

public CircularDependencyA( @Lazy CircularDependencyB circB) {

this .circB = circB;

}

}

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

4.3。使用Setter / Field Injection

最流行的解决方案之一也是 Spring提交的文件 ,是使用setter注入。

简单地说,如果你改变你的bean连接方法,使用。setter注入(或现场注入)而不是构造器注入 - 这确实解决了问题。这Spring就会创建bean,但在需要之前不要注入依赖项。

让我们这样做 - 让我们把类改为使用setter注入和另一个字段( 消息 )添加到 CircularDependencyB, 因此,我们可以执行适当的单元测试:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@Component

public class CircularDependencyA {

private CircularDependencyB circB;

@Autowired

public void setCircB(CircularDependencyB circB) {

this .circB = circB;

}

public CircularDependencyB getCircB() {

return circB;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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

}

}

现在我们必须对单元测试进行一些更改:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

@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已正确注入,请检查其 message 属性的值。

4.4。使用 @PostConstruct

另一种打破循环的方法是bean上使用 @Autowired 注入依赖项,然后使用 @PostConstruct 注释方法以设置其他依赖项。

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@Component

public class CircularDependencyA {

@Autowired

private CircularDependencyB circB;

@PostConstruct

public void init() {

circB.setCircA( this );

}

public CircularDependencyB getCircB() {

return circB;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@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。实现 ApplicationContextAwareInitializingBean

如果其中一个bean实现 ApplicationContextAware ,则bean可以访问Spring上下文,并且可以提取其他bean。实现 InitializingBean 我们指出这一点bean在设置所有属性后,必须执行某些操作。; 在这种情况下,我们希望手动设置依赖关系。

我们的bean代码为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@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初始化和注入,以及使用一种或另一种策略自己完成。

您可以找到上面显示的bean。 这里 、和单元测试 在这里

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门