一、循环依赖出现原因(单例setter注入)
A属性注入B,B属性注入A。存在A中有属性B,B中有属性A,那么当依赖注入的时候,就会产生当A还没有创建完的时候,由于对B的创建再次返回创建A,最终造成循环依赖。
@Component
public class A {
//单例的setter注入
@Autowired
private B b;
public void test(){
System.out.println(b);
}
}
@Component
public class B {
@Autowired
private A a;
public void dosth(){
a.test(); //输出b对象信息
}
}
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.igeek.ch03.singleton")
public class MyConfig {
}
@Component
@Aspect
public class LogAspect {
@Before("execution(* A.test())")
public void beforeAdvice(){
System.out.println("LogAspect A test()之前~");
}
}
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
a.test();*/
B b = ac.getBean(B.class);
b.dosth();
}
}
二、DefaultSingletonBeanRegistry 类
2.1 三级缓存
2.1.1 一级缓存
Map<beanName,bean实例> singletonObjects,一级缓存,又名"线程池",作用:存放进过完整生命周期(实例化、注入、初始化完成)的bean实例
2.1.2 二级缓存
Map<beanName,bean实例> earlySingletonObjects,二级缓存,存储实例化后的bean实例
2.1.3 三级缓存
Map<String,ObjectFactory<?>> singletonFactories lambda表达式,可以为beanName,value为ObjectFactor bean工厂。在出现循环依赖时,用于存储bean创建工厂,以便创建代理对象。
2.2 解决循环依赖最为核心的代码
DefaultSingletonBeanRegistry类中的 getSingleton() 方法
//解决循环依赖最核心的代码:一二级缓存找不到会去执行三级缓存的lambda表达式,并放入二级缓存!
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 先从一级缓存(单例池)中获取单实例bean
Object singletonObject = this.singletonObjects.get(beanName);
//若一级缓存中没有,且当前单实例bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//就会从二级缓存中查询
singletonObject = this.earlySingletonObjects.get(beanName);
//若二级缓存中也没有查询到
if (singletonObject == null && allowEarlyReference) {
//确保两个动作put、remove的原子性
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//就会从三级缓存中查找,只要是单实例bean,都会找到
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//若在三级缓存中找到单实例bean
if (singletonFactory != null) {
//则去执行Lambda表达式,即执行AOP,获得其代理对象
singletonObject = singletonFactory.getObject();
//将代理对象,存入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//并将其移除三级缓存,避免其他地方可以重复获取进行AOP操作
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
2.3 实例A创建过程 生命周期
singletonsCurrentlyInCreation add(A) ,A正是当前正在创建的bean
1.推断构造方法(优先使用无参构造方法),确定构造方法后,实例化目标对象A(new A()),如果提前暴露,则需要调用三级缓存的lambda表达式 Map<a,ObjectFactory>(将创建bean实例存放在二级缓存中,然后移出三级缓存中的lambda表达式,再从二级缓存中获取到bean实例).
2.属性注入 B,从一级缓存中查找B的实例,如果没有,则需要创建B的实例
B创建过程 生命周期
1.推断构造方法,实例化目标对象B
2.属性注入A,一级缓存中没有,并检测到singletonsCurrentlyInCreation中已经存在A,则 说明出现循环依赖问题,再从二级缓存中寻找A的实例;如果没有,从三级缓存 (ObjectFactory)中查找, 如果需要用到AOP,则是A的代理对象,如果不需要则是A的目 标对象;先存入二级缓存中,再移出三级缓存中的bean(确保单例),再从二级缓存中个获 取A的实例
3.初始化后,判断是否需要AOP,
4.将完整走完生命周期过程的实例bean,存入到 singletonObjects “一级缓存”单例池中
3. 初始化后,判断是否需要AOP(此时判断是否提起进行过AOP,在earlyProxyReferences中查找,若有,则可以从二级缓冲中查找A代理对象)
4. 将完整走完生命周期过程的实例bean,存入到 singletonObjects “一级缓存”单例池中
singletonsCurrentlyInCreation remove(A), 移出已创建完成的A。
2.4 二级缓存的作用
在2.3中我们可以看到,当A与B依赖共享时,二级缓存并没有起到作用,当三级缓存创建好bean实例放入二级缓存中后,B就从二级缓存中获取到A的实例,看似存入二级缓存是多余的步骤,实则不然。
如果存在第三个类C,A注入B与C,B与C都注入A,则当B注入A,且将A放入二级缓存中时,C再注入属性A就可以从二级缓存中直接获得(同时三级为了实现单例,三级缓存中A的lambda表达式singletonFactories在创建一个实例A后,便被移出,C也无法通过三级缓存获取到A的实例),确保了单例setter注入。
2.5 三级缓存的作用
二级缓存中只有beanName属性,bean实例不完全,如果想对实例对象进行增强,直接使用实例对象是行不通的,因此,需要在第三季缓存中添加ObjecFactory对象,bean工厂的lambda表达式,包含id、class、beanName、scope等多个属性,才能进行更多的操作。
3. 其他循环依赖
3.1 多例循环依赖(多例的setter注入)
解决方案:尽量使用单例setter注入方式
@Scope("prototype")
@Component
public class A {
@Autowired
private B b;
}
@Scope("prototype")
@Component
public class B {
@Autowired
private A a;
}
@ComponentScan(basePackages = "com.igeek.ch03.prototype")
public class MyConfig {
}
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
//多例setter注入,设置
//出现原因:A属性注入B,B属性注入A,出现循环依赖问题,Spring设计三级缓存不能够解决此问题,每一次获取实例bean都是新的
//BeanCurrentlyInCreationException: Requested bean is currently in creation: Is there an unresolvable circular reference?
//解决方案:尽量使用单例setter注入方式,又可以使用Spring的三级缓存
B b1 = ac.getBean(B.class);
B b2 = ac.getBean(B.class);
System.out.println(b1);
System.out.println(b2);
}
}
3.2 构造器注入
解决方法:使用@Lazy注解字 延迟加载
@Component
public class A {
private B b;
//延迟加载
@Lazy
@Autowired(required = false)
public A(B b){
this.b = b;
}
}
@Component
public class B {
private A a;
public B(A a){
this.a = a;
}
}
@ComponentScan(basePackages = "com.igeek.ch03.constructor")
public class MyConfig {
}
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
//构造器注入
//出现原因:A属性注入B,B属性注入A,出现循环依赖问题,Spring三级缓存不能解决此情况,原因是构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。
//BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?
//解决方案:@Lazy 延迟加载
B b = ac.getBean(B.class);
System.out.println(b);
}
}
3.3 DependsOn 循环依赖
有一种特殊的场景,比如我们需要在实例化Bean A之前,先实例化Bean B,这个实例就可以使用@DependsOn注解
@DependsOn("b")
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
@ComponentScan(basePackages = "com.igeek.ch03.dependson")
public class MyConfig {
}
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
//前置依赖 @DependsOn("b")
//出现原因:循环依赖 + 使用@DependsOn注解
//Circular depends-on relationship between 'b' and 'a'
//解决方案:不要在循环依赖发生的情况下,去使用@DependsOn ; 指定加载先后关系
B b = ac.getBean(B.class);
System.out.println(b);
}
}
四、总结
4.1 出现循环依赖如何捷俊
4.1.1 生成代理对象产生的循环依赖
1. 使用@Lazy注解,延迟加载
2.使用@DependsOn注解,指定加载先后顺序
3. 修改文件名称,改变循环依赖项的加载属性
4.1.2 使用@Depends参数的依赖地方
这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环一来就就可以解决
4.1.3 多例循环依赖
可以通过把bena改为单例的方法解决
4.1.4
这类依赖问题可以通过@Lazy来注解解决