SpringBoot构造器注入循环依赖及解决方案
文章主要介绍了SpringBoot构造器注入循环依赖及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教。
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
(解释一下,三个以上其实和两个是一样的,多加几个@lazy就可以了,等到后面bean创建成功再创建)
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。
当使用构造器注入时经常会发生循环依赖问题。如果使用其它类型的注入方式能够避免这种问题。
简单构造器注入循环依赖实例 项目结构如下:
1.首先定义两个相互通过构造器注入依赖的bean。
/**
* @Author: lixs
* @Date: 2021/4/6
* @Description: 循环依赖类A
*/
@Component
public
class
CircularDependencyA {
private
CircularDependencyB circB;
@Autowired
public
CircularDependencyA(
@Lazy
CircularDependencyB circB) {
this
.circB = circB;
}
}
/**
1. @Author: lixs
2. @Date: 2021/4/6
3. @Description: 循环依赖类B
*/
@Component
public
class
CircularDependencyB {
private
CircularDependencyA circA;
@Autowired
public
CircularDependencyB(
@Lazy
CircularDependencyA circA) {
this
.circA= circA;
}
}
2.创建配置类
/**
1. @Author: lixs
2. @Date: 2021/4/6
3. @Description: 配置类
*/
@Configuration
@ComponentScan
(basePackages = {
"com.li.springboot.bean"
})
public
class
TestConfig {
}
3.创建测试类
@SpringBootTest
@ContextConfiguration
(classes = {TestConfig.
class
})
class
ApplicationTests {
@Test
public
void
givenCircularDependency_whenConstructorInjection_thenItFails() {
// Empty test; we just want the context to load
}
}
正常运行结果:
上面就是报的是循环 依赖无法创建bean错误!
解决方案1.重新设计
重新设计结构,不用这种方式去创建bean。
2.使用注解 @Lazy
一种最简单的消除循环依赖的方式是通过延迟加载。
在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入。
/**
* @Author: lixs
* @Date: 2021/4/6
* @Description: 循环依赖类A
*/
@Component
public
class
CircularDependencyA {
private
CircularDependencyB circB;
@Autowired
public
CircularDependencyA(
@Lazy
CircularDependencyB circB) {
this
.circB = circB;
}
}
使用@Lazy后,运行代码,可以看到异常消除。
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;
}
}
测试类
@SpringBootTest
@ContextConfiguration
(classes = {TestConfig.
class
})
class
ApplicationTests {
@Autowired
ApplicationContext context;
//spring容器对象
@Bean
public
CircularDependencyA getCircularDependencyA() {
return
new
CircularDependencyA();
}
@Bean
public
CircularDependencyB getCircularDependencyB() {
return
new
CircularDependencyB();
}
@Test
public
void
testCircularDependency() {
//拿到bean对象
CircularDependencyA circA = context.getBean(CircularDependencyA.
class
);
//获取属性值
String result=circA.getCircB().getMessage();
System.out.println(result);
}
4.使用@PostConstruct
@PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
@Component
public
class
CircularDependencyA {
@Autowired
private
CircularDependencyB circB;
@PostConstruct
public
void
init() {
circB.setCircA(
this
);
}
public
CircularDependencyB getCircB() {
return
circB;
}
}
public
class
CircularDependencyB {
private
CircularDependencyA circA;
private
String message =
"Hi!"
;
public
void
setCircA(CircularDependencyA circA) {
this
.circA = circA;
}
public
String getMessage() {
return
message;
}
}
5.实现ApplicationContextAware与InitializingBean
(1)如果某个类实现了ApplicationContextAware接口,会在类初始化完成后调用setApplicationContext()方法进行操作
(2)如果某个类实现了InitializingBean接口,会在类初始化完成后,并在setApplicationContext()方法执行完毕后,调用afterPropertiesSet()方法进行操作
@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;
}
}
处理循环依赖有多种方式。
首先考虑是否能够通过重新设计依赖来避免循环依赖。
如果确实需要循环依赖,那么可以通过前文提到的方式来处理。
优先建议使用setter注入来解决。
以上为个人经验,希望能给大家一个参考