7、Spring源码-循环依赖

烟雨 4年前 (2021-07-28) 阅读数 41730 #Spring源码
文章标签 Spring
在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题
比如AService对象依赖了BService对象,BService对象依赖了AService对象。
@Component
public class AService {
	@Autowired
	private Bservice bService;

	public void test() {
		System.out.println(bService);
	}
}

@Component
public class BService {
	@Autowired
	private Aservice aService;

	public void test() {
		System.out.println(aService);
	}
}

image.png

那么如何打破这个循环,加个中间人(缓存)

image.png

A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓存,其他Bean需要时就可以从缓存中拿),放入缓存后,再进行依赖注入,此时A的Bean依赖了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B对象,然后把B的对象提早暴露出来放入缓存中,然后在对B的对象进行依赖注入A,此时能从缓存中拿到A的对象,B的对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。

但是Spring有个AOP的功能,是在初始化后处理的,使用的是AnnotationAwareAspectJAutoProxyCreator是xxxxBeanPostProcessor(Bean后置处理器)的某一个实现类。也是AbstractAutoProxyCreator的实现类,而在Spring中AOP利用的要么是JDK动态代理,要么CGLib的动态代理,所以如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。代理后就不是A的原始对象。如果只有一个缓存处理,显然无法满足AOP需求,因为最终需要的是代理对象

一、三级缓存

三级缓存是通用的叫法。

一级缓存为:singletonObjects缓存的是已经经历了完整生命周期的bean对象。

二级缓存为:earlySingletonObjects比singletonObjects多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。

三级缓存为singletonFactories缓存的是ObjectFactory,表示对象工厂,表示用来创建早期bean对象的工厂。

1.2、另外的缓存

earlyProxyReferences记录某个Bean是否进行了AOP,如果进行了AOP,就会放入这个缓存中
singletonsCurrentlyInCreation记录正在创建的Bean

二、分析

2.1、Bean的生成

简单描述一个Bean的生成:

A类--->生成一个普通对象-->属性注入-->基于切面生成一个代理对象(AOP)-->把代理对象放入singletonObjects单例池中

完整的生命周期前往:4、Spring源码-bean的生命周期

2.2、处理循环依赖中的AOP问题

AOP可以说是Spring中除开IOC的另外一大功能,而循环依赖又是属于IOC范畴的,所以这两大功能想要并存,Spring需要特殊处理。如何处理的,就是利用了singletonFactories(第三级缓存)

首先,singletonFactories(三级缓存)中存储的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories(三级缓存)中。

这个ObjectFactory是一个函数式接口,支持Lambda表达式:() -> getEarlyBeanReference(beanNamembdbean)

上面的Lambda表达式就是一个ObjectFactory,执行该Lambda表达式就会去执行getEarlyBeanReference()方法,而该方法如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

该方法会去执行SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference()方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator,一个是InstantiationAwareBeanPostProcessorAdapter,它的实现如下:

// InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
	return bean;
}
// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}
在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference()方法,而该类就是用来进行AOP的。

getEarlyBeanReference()方法首先得到一个cachekey,cachekey就是beanName。

然后把beanName和bean(这是原始对象)存入earlyProxyReferences中调用wrapIfNecessary进行AOP,得到并返回一个代理对象。

什么时候调用getEarlyBeanReference方法呢。回到循环依赖场景中,如图:

image.png

  1. A原始对象生成一个ObjectFactory:这个ObjectFactory就是上面说的labmda表达式,中间有getEarlyBeanReference方法(存入singletonFactories时并不会执行lambda表达式,也就是不会执行getEarlyBeanReference方法)

  2. 需要A(三级缓存singletonFactories中获取)从singletonFactories(三级缓存)根据beanName得到一个ObjectFactory,然后执行ObjectFactory,也就是执行getEarlyBeanReference方法,此时会得到一个A原始对象经过AOP之后的代理对象,然后把该代理对象放入earlySingletonObjects(二级缓存)中,还要放入earlyProxyReferences缓存中,表示已经经过AOP了。

    1. 注意此时,我们只得到了A原始对象的代理对象,这个对象还不完整,因为A原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入singletonObjects(一级缓存)中

当B创建完了之后,A继续进行生命周期,而A在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道A原始对象已经经历过了AOP,所以对于A本身而言,不会再去进行AOP了,那么怎么判断一个对象是否经历过了AOP呢?会利用上文提到的earlyProxyReferences,在AbstractAutoProxyCreator的postProcessAfterInitialization方法中,会去判断当前beanName是否在earlyProxyReferences,如果在则表示已经提前进行过AOP了,无需再次进行AOP。

image.png

对于A对象而言,进行了AOP的判断后,以及BeanPostProcessor(Bean的后置处理器)的执行之后,就需要把A对应的对象放入singletonObjects(一级缓存)中,此时需要从earlySingletonObjects(二级缓存)中得到代理对象,然后入singletonObjects(一级缓存)中。

三、总结

20220206162059164413565956594.png

  1. singletonObjects:缓存经过了完整生命周期的bean。

  2. earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖,就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects(二级缓存)中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects(二级缓存)中,否则就是把原始对象放入earlySingletonObjects(二级缓存),但是不管怎么样,就是是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,所以放入earlySingletonObjects(二级缓存)我们就可以统一认为是未经过完整生命周期的bean。

  3. singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,那么这个Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects(一级缓存)中,如果当前bean在依赖注入时发现出现了循环依赖,则从三级缓存中拿到Lambda表达式,并执行Lambda表达式得到一个对象,并把得到的对象放入二级缓存((如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象))。

  4. earlyProxyReferences:用来记录某个原始对象是否进行过AOP了。

  5. singletonsCurrentlyInCreation:是一个Set<String>,记录正在创建的Bean的BeanName。


版权声明

非特殊说明,本文由Zender原创或收集发布,欢迎转载。

评论列表
  •   虚空架构师  发布于 2022-01-06 14:31:10  回复该评论
    按道理,二级缓存也可以做代理,把代理工厂和labmda表达式一同放进二级缓存,这样就可以省去一个缓存,为啥还要弄个三级缓存?
    •   zender  发布于 2022-01-10 11:24:16  回复该评论
      Spring的生命周期中AOP是在Bean初始化后才进行的增强,如果使用二级缓存解决循环依赖和AOP增强问题,就需要在Bean初始化时就要进行增强,这样就违背了Spring自己设计的生命周期流程。所以才会有三级缓存来解决AOP。
    •   Bruce  发布于 2022-01-07 09:34:15  回复该评论
      缓存功能单一性吧,多个功能可能导致不确定性。
  •   小桃  发布于 2021-12-06 16:41:10  回复该评论
    给UP总结一下:三级缓存不是用来存bean,二用来存函数接口,去解决aop创建动态代理时的循环依赖,其实真正的目的是为了解耦,二级缓存就能解决循环依赖,三级缓存目的就是生成代理对象,放入到二级缓存。
    •   zender  发布于 2021-12-06 19:27:50  回复该评论
      是的,一级缓存存储创建完毕的Bean,二级缓存解决循环依赖,三级缓存解决AOP。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

作者文章
热门