spring源码学习笔记(三)我所理解的循环依赖以及模拟手写
每天多学一点点~
话不多说,这就开始吧…
文章目录
1.前言
什么是循环依赖?网上很多博客都已经说的很清楚了,这里简单说一下。并谈谈自己的理解。不对之处欢迎讨论指正。
A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。它们之间的依赖关系如下:
2. spring中循环依赖源码
org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(java.lang.Class<?>...)
--->org.springframework.context.support.AbstractApplicationContext#refresh
---> org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
--->org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
--->org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)
--->org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
--->org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
--->org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
getSingleton(String beanName, boolean allowEarlyReference)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
/**
* 第一步:我们尝试去一级缓存(单例缓存池中去获取对象,一般情况从该map中获取的对象是直接可以使用的)
* IOC容器初始化加载单实例bean的时候第一次进来的时候 该map中一般返回空
*/
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 若在第一级缓存中没有获取到对象,并且singletonsCurrentlyInCreation这个list包含该beanName
* IOC容器初始化加载单实例bean的时候第一次进来的时候 该list中一般返回空,但是循环依赖的时候可以满足该条件
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
/**
* 尝试去二级缓存中获取对象(二级缓存中的对象是一个早期对象)
* 何为早期对象:就是bean刚刚调用了构造方法,还来不及给bean的属性进行赋值的对象
* 就是早期对象
*/
singletonObject = this.earlySingletonObjects.get(beanName);
/**
* 二级缓存中也没有获取到对象,allowEarlyReference为true(参数是有上一个方法传递进来的true)
*/
if (singletonObject == null && allowEarlyReference) {
/**
* 直接从三级缓存中获取 ObjectFactory 对象 这个对接就是用来解决循环依赖的关键所在
* 在ioc后期的过程中,当bean调用了构造方法的时候,把早期对象包裹成一个ObjectFactory
* 暴露到三级缓存中
*/
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//从三级缓存中获取到对象不为空
if (singletonFactory != null) {
/**
* 在这里通过暴露的ObjectFactory 包装对象中,通过调用他的getObject()来获取我们的早期对象
* 在这个环节中会调用到 getEarlyBeanReference()来进行后置处理
*/
singletonObject = singletonFactory.getObject();
//把早期对象放置在二级缓存,
this.earlySingletonObjects.put(beanName, singletonObject);
//ObjectFactory 包装对象从三级缓存中删除掉
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
3. 如何解决循环依赖
想都不用想,三级缓存
/** 一级缓存 这个就是我们大名鼎鼎的单例缓存池 用于保存我们所有的单实例bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级缓存 ,用户缓存我们的key为beanName value是我们的早期对象(对象属性还没有来得及进行赋值) */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 三级缓存 该map用户缓存 key为 beanName value 为ObjectFactory(包装为早期对象)存的是函数接口ObjectFactory,函数式接口 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
- 创建原始 bean 对象
instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
假设 beanA 先被创建,创建后的原始对象为BeanA@1234,上面代码中的 bean 变量指向就是这个对象。
-
暴露早期引用
该方法用于把早期对象包装成一个ObjectFactory 暴露到三级缓存中 用于将解决循环依赖
beanA 指向的原始对象创建好后,就开始把指向原始对象的引用通过 ObjectFactory 暴露出去。getEarlyBeanReference 方法的第三个参数 bean 指向的正是 createBeanInstance 方法创建出原始 bean 对象 BeanA@1234。 -
解析依赖
populateBean(beanName, mbd, instanceWrapper);
populateBean 用于向 beanA 这个原始对象中填充属性,当它检测到 beanA 依赖于 beanB 时,会首先去实例化 beanB。
beanB 在此方法处也会解析自己的依赖,当它检测到 beanA 这个依赖,于是调用 BeanFactroy.getBean(“beanA”) 这个方法,从容器中获取 beanA。 -
获取早期引用
就是上面getSingleton(String beanName, boolean allowEarlyReference)源码逻辑。
接着上面的步骤讲:
1.populateBean 调用 BeanFactroy.getBean(“beanA”) 以获取 beanB 的依赖。
2.getBean(“beanB”) 会先调用 getSingleton(“beanA”),尝试从缓存中获取 beanA。此时由于 beanA 还没完全实例化好
3.于是 this.singletonObjects.get(“beanA”) 返回 null。
4.接着 this.earlySingletonObjects.get(“beanA”) 也返回空,因为 beanA 早期引用还没放入到这个缓存中。
5.最后调用 singletonFactory.getObject() 返回 singletonObject,此时 singletonObject != null。singletonObject 指向 BeanA@1234,也就是 createBeanInstance 创建的原始对象。此时 beanB 获取到了这个原始对象的引用,beanB 就能顺利完成实例化。beanB 完成实例化后,beanA 就能获取到 beanB 所指向的实例,beanA 随之也完成了实例化工作。由于 beanB.beanA 和 beanA 指向的是同一个对象 BeanA@1234,所以 beanB 中的 beanA 此时也处于可用状态了。
4. 面试常问问题总结
4.1 简单的描述一下spring是如何解决循环依赖的?
-
尝试创建bean singletonA,发现singletonA是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个A的实例,并保存对它的引用,并且将singletonA标记为“正在创建中的singleton”。然后发现singletonA依赖了singletonB,所以尝试创建singletonB。
-
尝试创建bean singletonB,发现singletonB是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个B的实例,并保存对它的引用,并且将singletonB标记为“正在创建中的singleton”。然后发现singletonB依赖了singletonA,所以尝试创建singletonA。
-
尝试创建singletonA,注意,这时Spring容器发现singletonA“正在创建中”,那么就不会再去创建singletonA,而是返回容器之前保存了的对singletonA的引用。(即上面代码判断–> 先从一级拿,拿不到,去二级,二级没有,去三级,调钩子方法)
-
容器将singletonA通过setter方法注入到singletonB,从而singletonB完成创建。
-
容器将singletonB通过setter方法注入到singletonA,从而singletonA完成创建。
上述步骤,最重要的是第1步和第3步。在第1步中,容器会保存对singletonA的引用,在第3步中,再返回对singletonA的引用,从而可以成功创建那些依赖了singletonA的bean(本例中是singletonB)。这样,循环依赖的环就在singletonA这个点这里被打破。
即:
“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况: A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象,即调用钩子函数(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,长大成人,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象也蜕变完美了!一切都是这么神奇!!
那为什么 prototype 不能成为打破这个环的一个点呢?原因就在于Spring容器只会对singleton保存引用,而对于prototype,并不会对其保存引用,这就导致在第3步中并不能获得之前创建的bean(因为引用不到它)。还有就是,prototype 多例压根就不会放到缓存中。
4.2 为何需要三级缓存,一级、两级能不能实现?
按道理来讲,个人认为一级缓存都能实现,但这就好比你为何不把所有代码卸载一个类中一样。
性能低,耦合度高,扩展性差。比如,循环依赖下的动态代理都要放在一级缓存中,并且如果存在重复依赖要多次创建动态代理。而且为了变读取不到完整的bean在整个创建过程中都要上锁,代码编程串行,性能大大降低等等。
二级缓存作用:只要是为了分离成熟Bean和纯净Bean(未注入属性)的存放, 防止多线程中在Bean还未创建完成时读取到的Bean时不完整的。所以也是为了保证我们getBean是完整最终的Bean,不会出现不完整的情况。个人理解二级缓存也能解决aop代理问题。但是为了解耦,并且为了单一原则(比如aop是在 初始化之后的bpp中扩展点中做的)
如何判断是不是循环依赖?二级缓存有,就是。
三级缓存作用:利用函数式接口,将createBean的逻辑和getBean的逻辑解耦。
Bean的aop动态代理创建时在初始化之后,但是循环依赖的Bean如果使用了AOP,那就无法等到解决完循环依赖再创建动态代理, 因为这个时候已经注入属性。 所以如果循环依赖的Bean使用了aop. 需要提前创建aop。
4.3 为什么Spring不能解决构造器的循环依赖?
在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。
4.4 为什么多例Bean不能解决循环依赖?
从spring源码可知,核心是利用一个map,来解决这个问题的,这个map就相当于缓存。
为什么可以这么做,因为我们的bean是单例的,而且是字段注入(setter注入)的,单例意味着只需要创建一次对象,后面就可以从缓存中取出来,字段注入,意味着我们无需调用构造方法进行注入。
如果是原型bean,那么就意味着每次都要去创建对象,无法利用缓存;
如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。
4.5 循环依赖可以关闭吗
可以,Spring提供了这个功能,我们需要这么写:
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
// 关闭循环依赖
applicationContext.setAllowCircularReferences(false);
applicationContext.register(AppConfig.class);
applicationContext.refresh();
5.模拟手写
- ObjectFactory类 模拟三级缓存钩子函数
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
- IApi 接口
public interface IApi {
void say();
}
- 两个示例 InstanceA 和 InstanceB
@Component
//@Scope("prototype")
public class InstanceA implements IApi {
@Autowired
private InstanceB instanceB;
public InstanceB getInstanceB() {
return instanceB;
}
public void setInstanceB(InstanceB instanceB) {
this.instanceB = instanceB;
}
public InstanceA(InstanceB instanceB) {
this.instanceB = instanceB;
}
public InstanceA() {
System.out.println("InstanceA实例化");
}
@Override
public void say() {
System.out.println("I'm A");
}
}
@Component
public class InstanceB {
@Autowired
private InstanceA instanceA;
public InstanceA getInstanceA() {
return instanceA;
}
public void setInstanceA(InstanceA instanceA) {
this.instanceA = instanceA;
}
public InstanceB(InstanceA instanceA) {
this.instanceA = instanceA;
}
public InstanceB() {
System.out.println("InstanceB实例化");
}
public void bb() {
System.out.println("I'm bb");
}
}
- JdkProxyBeanPostProcessor 类,模拟动态代理
public class JdkProxyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
/**
* 模拟代理 (但是有个问题,最后 declaredField.set 时候类型会错) 所以这里先注释,
* 若实现aop,则真正会调用到SmartInstantiationAwareBeanPostProcessor 的实现类 AbstractAutoProxyCreator 的 getEarlyBeanReference
*
* @param bean the raw bean instance
* @param beanName the name of the bean
* @return
* @throws BeansException
*/
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
// 假设:A 被切点命中 需要创建代理 @PointCut("execution(* *..InstanceA.*(..))")
if (bean instanceof InstanceA) {
System.out.println("模拟 A 的 JdkProxyBeanPostProcessor 逻辑");
}
return bean;
}
}
- MainStart类,模拟循环依赖
package com.zjq.circulardependencies;
import com.sun.javafx.logging.PulseLogger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/* ━━━━━━佛祖保佑━━━━━━
* ,;,,;
* ,;;'( 社
* __ ,;;' ' \ 会
* /' '\'~~'~' \ /'\.) 主
* ,;( ) / |. 义
*,;' \ /-.,,( ) \ 码
* ) / ) / )| 农
* || || \)
* (_\ (_\
* ━━━━━━永无BUG━━━━━━
* @author :zjq
* @date :2020/9/21 19:14
* @description: TODO 模拟循环依赖 spring只能解决单例下 set的循环依赖
* @version: V1.0
* @slogan: 天下风云出我辈,一入代码岁月催
*/
public class MainStart {
private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
/**
* 读取bean定义,当然在spring中肯定是根据配置 动态扫描注册
*/
public static void loadBeanDefinitions() {
RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
beanDefinitionMap.put("instanceA", aBeanDefinition);
beanDefinitionMap.put("instanceB", bBeanDefinition);
}
public static void main(String[] args) throws Exception {
// 加载了BeanDefinition
loadBeanDefinitions();
// 注册Bean的后置处理器
// getBean("instanceA")或者getBean("instanceB"); 时候一级缓存中2个都有了
InstanceA instanceA = (InstanceA) getBean("instanceA");
instanceA.say();
InstanceB instanceB = (InstanceB) getBean("instanceB");
instanceB.bb();
}
// 一级缓存 单例池 成熟态Bean
public static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存: 为了将 成熟Bean和纯净Bean分离,避免读取到不完整得Bean
// 二级缓存 纯净态Bean (存储不完整的Bean用于解决循环依赖中多线程读取一级缓存的脏数据)
public static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
// 三级缓存
public static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();
// 循环依赖标识(打上正在创建的标识)
public static Set<String> singletonsCurrennlyInCreation = new HashSet<>();
// 获取早期对象(模拟)
public static Map<String, Object> beanWrapper = new ConcurrentHashMap<>();
// 假设A 使用了Aop @PointCut("execution(* *..InstanceA.*(..))") 要给A创建动态代理
// 获取Bean
public static Object getBean(String beanName) throws Exception {
Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
// createBean
Object instanceBean;
// 多线程下 避免读取到不完整Bean的 第一把锁 把整个创建过程包起来
synchronized (singletonObjects) {
// 加这个判断 也是为了 避免读取到不完整Bean的
if (singletonObjects.containsKey(beanName)) {
return singletonObjects.get(beanName);
}
// 实例化
RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
Class<?> beanClass = beanDefinition.getBeanClass();
instanceBean = beanClass.newInstance(); // 通过无参构造函数
// 标记为 正在创建
if (!singletonsCurrennlyInCreation.contains(beanName)) {
singletonsCurrennlyInCreation.add(beanName);
beanWrapper.put(beanName, instanceBean);
}
// 放入三级缓存
// 函数式 接口 lambada 写法 只有在调用 ObjectFactory.getObject时候才会回调这里
singletonFactories.put(beanName, () ->
new JdkProxyBeanPostProcessor().getEarlyBeanReference(beanWrapper.get(beanName), beanName));
// 函数式 接口 原始 写法
/* singletonFactories.put(beanName, new ObjectFactory() {
@Override
public Object getObject() throws BeansException {
return new JdkProxyBeanPostProcessor().getEarlyBeanReference(beanWrapper.get(beanName), beanName));
;
}
});*/
// 添加到二级缓存
// singletonFactories.put(beanName, instanceBean);
// 属性赋值
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Autowired annotation = declaredField.getAnnotation(Autowired.class);
// 说明属性上面有Autowired
if (annotation != null) {
declaredField.setAccessible(true);
// 模拟 直接byname bytype byconstrator
// instanceB
String name = declaredField.getName();
Object fieldObject = getBean(name); // 递归 拿到B得Bean
declaredField.set(instanceBean, fieldObject);
}
}
// 初始化 init-mthod
// 放在这里创建已经完了 B里面的A 不是proxy
// 正常情况下会再 初始化之后创建proxy
// 由于递归完后A 还是原实例, 所以要从二级缓存中拿到proxy 。
if (earlySingletonObjects.containsKey(beanName)) {
instanceBean = earlySingletonObjects.get(beanName);
}
// 添加到一级缓存 A
singletonObjects.put(beanName, instanceBean);
// remove 二级缓存和三级缓存
earlySingletonObjects.clear();
singletonFactories.clear();
}
return instanceBean;
}
public static Object getSingleton(String beanName) {
// 先从一级缓存中拿
Object bean = singletonObjects.get(beanName);
// 说明是循环依赖
if (bean == null && singletonsCurrennlyInCreation.contains(beanName)) {
bean = earlySingletonObjects.get(beanName);
// 如果二级缓存没有就从三级缓存中拿
if (bean == null) {
// 多线程 下 避免读取到不完整Bean的 第二把锁
synchronized (singletonObjects) {
// 从三级缓存中拿
ObjectFactory singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
// 拿到AOP动态代理 在这里通过暴露的ObjectFactory 包装对象中,通过调用他的getObject()来获取我们的早期对象
bean = singletonFactory.getObject();
//把早期对象放置在二级缓存,
earlySingletonObjects.put(beanName, bean);
//ObjectFactory 包装对象从三级缓存中删除掉
singletonFactories.remove(beanName);
}
}
}
}
return bean;
}
}
运行main方法打印
InstanceA实例化
InstanceB实例化
模拟 A 的 JdkProxyBeanPostProcessor 逻辑
I'm A
6.结语
世上无难事,只
怕有心人,每天积累一点点,fighting!!!
2021,加油,fighting,希望可以少些crud啦!