什么是轮回依赖以及处理方式版权声明
原创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。实现 ApplicationContextAware 和 InitializingBean
如果其中一个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。 这里 、和单元测试 在这里 。
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除