什么是循环依赖

相互依赖,A类对象中有B类属性,B类对象中有A类属性。两者相互引用,依赖。

// A依赖了B
class A{
    public B b;
}

// B依赖了A
class B{
    public A a;
}

如何关闭循环依赖

public class TestApp {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
 		DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
		// 设置不允许循环依赖
		beanFactory.setAllowCircularReferences(false);
		applicationContext.register(AppConfig.class);
		applicationContext.refresh();
    }
}

java 循环依赖问题怎么解决 jvm 循环依赖_lambda表达式

循环依赖的条件

1.依赖注入的方式不能全是构造器注入
2.容器允许循环依赖

Spring循环依赖解决描述

生命周期

1。创建原始对象
2。填充属性
3。执行初始化后后置处理器(aop)
4。添加到单例池

循环步骤解析

只有一个Map

按照如下分析,如果只需要一个map(单例池),是会出现死循环的。

java 循环依赖问题怎么解决 jvm 循环依赖_AOP_02

两个Map

如果使用两个Map,一个放单例bean,一个放原始对象,解决。

java 循环依赖问题怎么解决 jvm 循环依赖_java 循环依赖问题怎么解决_03

看样子似乎满足了,但是上面存在最大的问题,如果A类使用了AOP,执行完填充属性后,然后执行初始化后后置处理器步骤,可能A类最后放到单例bean中的是代理对象,而B对象持有的A类引用是原始对象,所以只能在创建原始对象中放代理对象到Map中(也就是提前AOP),这样就能解决后面的代理对象。

优化两个Map-v1

java 循环依赖问题怎么解决 jvm 循环依赖_java 循环依赖问题怎么解决_04

看样子满足了,但是又出来一个问题,并不是所有的类都需要代理对象(也就是需要得到提前AOP的代理对象),那么存A的代理对象到Map就存在动态,可能放的是原始对象或者是代理对象。那么只有在从第二个map获取A的时候,采取判断当前是需要原始对象还是代理对象,这个逻辑明显需要一个函数来实现,所以可优化用lambda,在需要的时候获取执行即可。

优化两个Map-v2

java 循环依赖问题怎么解决 jvm 循环依赖_缓存_05

现在来看已经满足了,能够满足如果产生循环依赖,如果需要提前aop或者不进行aop的属性填充。
但是存在一个问题,如果A类中又来了一个C类型属性C填充,也产生了循环依赖,那么如何?很明显会类似属性B填充的逻辑,但是到最后一个步骤,又会重新执行一次lambda的表达式获取A的原始对象或者A的代理对象,但是在注入属性B的时候已经执行一遍了,这里就没有必要重新执行一遍了生成一个新的aop对象,所以需要将这个lambda表达式的结果缓存到一个map中,也就是使用第三个map,后面再循环注入直接先从第三个map中获取是否存在。

优化三个Map-v3

具体看填充属性C.先从单例map中获取A,如果不存在,则判断第三个map中是否存在A的代理对象或者原始对象,如果没有,则从第二个map中获取A的lambda表达式执行获取A的代理对象或者原始对象。

java 循环依赖问题怎么解决 jvm 循环依赖_AOP_06

由此,如果完成循环依赖,至少需要三个Map,一个存放单例bean,一个放lambda表达式,另一个缓存提前aop或者原始对象。

源码分析

定义的三个Map

/** Cache of singleton objects: bean name to bean instance. */
//存放单例bean  一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
//放lambda表达式  二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
//缓存提前aop或者原始对象  三级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

创建原始对象,添加lambda(三级缓存Map)

java 循环依赖问题怎么解决 jvm 循环依赖_AOP_07

addSingletonFactory()方法的内部实现

注意在添加这个方法之前,有一个判断,判断当前beanDefinition的是不是定义的单例,是否允许循环依赖以及当前beanName是不是在正在创建中集合(isSingletonCurrentlyInCreation(beanName)) 只有三者成立的情况下才会添加。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			// 如果当前bean不存在单例
			if (!this.singletonObjects.containsKey(beanName)) {
			 //添加lambda
			this.singletonFactories.put(beanName, singletonFactory);
			// 删除lambda执行后的缓存的对象,就算不存在
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}
public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}

从第三个Map中获取(二级缓存Map)

会发现首先从一级缓存中拿,如果一级缓存不存在且在创建中。从二级缓存Map拿,如果不存在,会调用lambda表达式调用获取。

java 循环依赖问题怎么解决 jvm 循环依赖_AOP_08

问题

提前进行了aop,后面初始化后后置处理器如何判断已经提前了?

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference

因为在调用第三级缓存的lambda表达式的时候,会将当前提前进行aop的beanName缓存起来,使用earlyProxyReferences,类型是Map.

// 出现了循环依赖
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName); // beanName  aService
    // earlyProxyReferences 会用来判断当前bean是否还需要进行aop
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);  //返回代理对象
}

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

在执行初始化后后置处理器的postProcessAfterInitialization()方法时会使用earlyProxyReferences来判断是否已经提前进行了aop。

@Override
// 正常情况进行AOP的地方
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // earlyProxyReferences中存的是哪些提前进行了AOP的bean,beanName:AOP之前的对象
        // 注意earlyProxyReferences中并没有存AOP之后的代理对象  BeanPostProcessor
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 没有提前进行过AOP,则进行AOP
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    // 为什么不返回代理对象呢? 因为是null,无需处理
    return bean;   //
}

为什么在下表中的第二种情况的循环依赖能被解决,而第四种情况不能被解决呢?

依赖描述

依赖注入方式

循环依赖是否被解决

AB相互依赖(循环依赖)

均采用setter方法注入


AB相互依赖(循环依赖)

A中注入B的方式为setter方法,B中注入A的方式为构造器


AB相互依赖(循环依赖)

均采用构造器注入


AB相互依赖(循环依赖)

B中注入A的方式为setter方法,A中注入B的方式为构造器


Spring在创建Bean时默认会根据自然排序进行创建,所以A会先于B进行创建。

@Component
public class A {
	 @Autowired
	public A(B b){

	 }
}
@Component
public class B {
	@Autowired
	private A a;
}

运行结果如图:

java 循环依赖问题怎么解决 jvm 循环依赖_lambda表达式_09


那么有解决方案吗? 有的,在注入B的时候,注入代理即可。

@Component
public class A {
	 private B b;
	 @Autowired
	 @Lazy
	public A(B b){
		this.b=b;
	 }
}

最后结果分析图

java 循环依赖问题怎么解决 jvm 循环依赖_AOP_10

总结

循环依赖至少三个Map;
1.单例池(一级缓存)
2.缓存代理对象或者原始对象(二级缓存)
3.Lambda表达式Map(三级缓存)
4.earlyProxyReferences属性Map记录是否提前进行了AOP.
5.singletonsCurrentlyInCreation记录当前正在创建的beanName