1,什么是循环依赖?
发生在bean A依赖于另一个bean B时,bean B依赖于bean A;
2,Sping中发生了什么?
当Spring上下文加载所有bean时,它会尝试按照它们完全工作所需的顺序创建bean。例如,如果我们没有循环依赖,如下例所示:
A->B->C
Spring将创建bean C,然后创建bean B(并将bean注入其中),然后创建bean A(并将bean B注入其中)。
但是,当具有循环依赖时,Spring无法决定应该首先创建哪个bean,因为它们彼此依赖。在这些情况下,Spring将在加载上下文时引发BeanCurrentlyInCreationException。
使用构造函数注入时,它可能发生在Spring中; 如果您使用其他类型的注入,则不应该发现此问题,因为依赖项将在需要时注入,而不是在上下文加载时注入。
3 Demo
让我们定义两个相互依赖的bean(通过构造函数注入)
package com.evan.springboot.study;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author evanYang
* @version 1.0
* @date 2020/05/19 21:46
*/
@Component
@NoArgsConstructor
public class DependecyA {
private DependecyB dependecyB;
@Autowired
public DependecyA(DependecyB dependecyB){
this.dependecyB=dependecyB;
}
}
package com.evan.springboot.study;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author evanYang
* @version 1.0
* @date 2020/05/19 21:46
*/
@Component
@NoArgsConstructor
public class DependecyB {
private DependecyA dependecyA;
@Autowired
public DependecyB(DependecyA dependecyA){
this.dependecyA=dependecyA;
}
}
现在我们可以为测试编写一个Configuration类,让我们称之为DemoConfig ,它指定要扫描组件的基础包。假设我们的bean在包“ com.evan.springboot.study ” 中定义:
package com.evan.springboot.study;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author evanYang
* @version 1.0
* @date 2020/05/19 21:48
*/
@Configuration
@ComponentScan(basePackages = {"com.evan.springboot.study"})
public class DemoConfig {
}
最后我们可以编写一个JUnit测试来检查循环依赖。测试可以为空,因为在上下文加载期间将检测循环依赖性。
package com.evan.springboot;
import com.evan.springboot.study.DemoConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author evanYang
* @version 1.0
* @date 2020/05/19 21:49
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = { DemoConfig.class })
public class Demo {
@Test
public void demoTest() {
// Empty test; we just want the context to load
}
}
4解决方案
4.2。使用@Lazy
打破循环的一个简单方法是让Spring懒洋洋地初始化其中一个bean。那就是:它不是完全初始化bean,而是创建一个代理将它注入另一个bean。注入的bean只有在第一次需要时才会完全创建。
要使用我们的代码尝试此操作,您可以将DependecyA更改为以下内容:
@Component
public class DependecyA {
private DependecyB circB;
@Autowired
public DependecyA(@Lazy DependecyB circB) {
this.circB = circB;
}
}
4.3。使用Setter / Field Injection
最流行的解决方法之一,也是Spring文档提出的,是使用setter注入。
简单地说,如果你改变你的bean的连接方式,使用setter注入(或现场注入)而不是构造函数注入 - 这确实解决了这个问题。这样Spring就会创建bean,但是在需要之前不会注入依赖项。
让我们这样做 - 让我们改变我们的类以使用setter注入,并将另一个字段(消息)添加到CircularDependencyB,以便我们可以进行适当的单元测试:
@Component
public class DependecyA {
private DependecyB circB;
@Autowired
public void setCircB(DependecyB circB) {
this.circB = circB;
}
public DependecyB getCircB() {
return circB;
}
}
4.4。使用@PostConstruct
打破循环的另一种方法是在其中一个bean上使用@Autowired注入依赖项,然后使用@PostConstruct注释的方法来设置其他依赖项。
我们的bean可以有以下代码:
@Component
public class DependencyA {
@Autowired
private DependencyB circB;
@PostConstruct
public void init() {
circB.setCircA(this);
}
public DependencyB getCircB() {
return circB;
}
}