Spring Boot版本
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/>
</parent>
Spring Boot 2.6.0之后,Spring不会自动处理循环依赖的问题,需要设置开启。
setter注入导致循环依赖(支持)
@Service
public class A {
@Autowired
private B b;
public void test() {}
}
@Service
public class B {
@Autowired
private A a;
public void test() {}
}
Spring解决循环依赖流程
1. Spring容器开始创建A,标记A为正在创建中,反射创建实例,A对象工厂放入三级缓存
2. 初始化A实例时发现需要依赖注入B,标记B为正在创建中,反射创建实例,B对象工厂放入三级缓存
3. 初始化B实例时发现需要依赖注入A,依次从一级、二级、三级缓存中取值,一级和二级缓存都没有,通过三级缓存中A对象工厂的getObject方法获得A实例,把该A实例放入二级缓存,删除对应的三级缓存
4. B实例初始化完成后放入一级缓存,删除二级和三级对应的缓存
5. 回到第2步,A实例初始化完成后放入一级缓存,删除二级对应的缓存
一级缓存、二级缓存和三级缓存数据结构
从缓存中获取bean
对象工厂和getObject方法
Spring引入三级缓存来解决这个问题(代码方便扩展),一级缓存也能搞定(如果存在AOP增强,那么一级缓存里面直接存代理类对象)。
一级缓存存放初始化完成的bean;
二级缓存是临时缓存,存放三级缓存经过BeanPostProcessor实现类增强处理后的代理对象;
三级缓存是临时缓存,存放持有原始bean(直接new出来的bean,属性未赋值)的对象工厂。
构造器注入导致循环依赖(不支持)
@Service
public class A {
public A(B b) {}
}
@Service
public class B {
public B(A a) {}
}
在准备创建A对象时,发现构造方法需要B对象。
在准备创建B对象时,发现构造方法需要A对象。
最开始的A对象没有创建,无法解决。
Spring 4.x之后推荐使用构造器注入,防止循环依赖。
@Lazy避免注入最终版本,真正使用时才注入最终版本。
通过一级缓存来解决循环依赖问题
import lombok.Getter;
import lombok.Setter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class MySpring {
// 同singletonObjects,缓存beanName和实例
private Map<String, Object> beanInstanceMap = new HashMap<>();
// 保存beanName对应的class对象
private Map<String, Class> beanDefinationMap = new HashMap<>();
// 保存bean之间的引用关系
private Map<String, String> beanPropertyMap = new HashMap<>();
private void init() {
beanDefinationMap.put("testA", TestA.class);
beanDefinationMap.put("testB", TestB.class);
beanDefinationMap.put("testC", TestC.class);
beanPropertyMap.put("testA", "testB");
beanPropertyMap.put("testB", "testC");
beanPropertyMap.put("testC", "testA");
}
private Object getBean(String beanName) throws Exception {
// 1.从缓存中获取
Object instance = beanInstanceMap.get(beanName);
if (instance == null) {
// 2.反射创建实例
instance = beanDefinationMap.get(beanName).newInstance();
// 3.缓存bean
beanInstanceMap.put(beanName, instance);
// 4.注入bean中的属性
String property = beanPropertyMap.get(beanName);
if (!(property == null || property.length() == 0)) {
Object propertyValue = getBean(property);
setPropertyValue(instance, property, propertyValue);
}
}
return instance;
}
private void setPropertyValue(Object instance, String property, Object propertyValue) throws Exception {
Field field = instance.getClass().getDeclaredField(property);
field.setAccessible(true);
field.set(instance, propertyValue);
}
public static void main(String[] args) throws Exception {
MySpring spring = new MySpring();
spring.init();
TestA testA = (TestA) spring.getBean("testA");
// true
System.out.println(testA.getTestB().getTestC().getTestA() == testA);
}
@Getter
@Setter
static class TestA {
TestB testB;
}
@Getter
@Setter
static class TestB {
TestC testC;
}
@Getter
@Setter
static class TestC {
TestA testA;
}
}
参考资料
【spring源码深度解析】别再盲目的说spring有三级缓存了,两个缓存只是启动时为了解决循环依赖,spring启动后只有一个缓存有用
https://zhuanlan.zhihu.com/p/358802637
【超级干货】为什么spring一定要弄个三级缓存?
https://zhuanlan.zhihu.com/p/496273636