帮你深度探寻Spring循环依赖源码实现!万字长文你值得拥有!

package simulation;

import simulation.annotations.MyAutowired;
import simulation.service.AService;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**

  • 模拟Spring解决循环依赖的问题
  • @author huangfu
    */
    public class DebugTest {

/**

  • 已经完全创建好的
    /
    private final Map<String,Object> singletonObject = new HashMap<>(8);
    /
    *
  • 创建一半但是没有属性注入的
    */
    private final Map<String,Object> earlySingletonObjects = new HashMap<>(8);

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
DebugTest debugTest = new DebugTest();
AService bean = debugTest.getBean(AService.class);
System.out.println(bean);
}

/**

  • 获取一个bean对象
  • @param tClass
  • @return
    */
    public T getBean(Class tClass) throws InstantiationException, IllegalAccessException {
    //先查询一级缓存是否有数据
    String beanName = getBeanName(tClass);
    Object object = singletonObject.get(beanName);
    //一级缓存没有在查询二级缓存是否有数据
    if(object == null){
    object = earlySingletonObjects.get(beanName);
    if(object == null) {
    //两个缓存都没有就创建类
    object = createBean(tClass,beanName);
    }
    }
    return (T)object;
    }

/**

  • 创建一个bean
  • @param tClass
  • @param beanName
  • @return
    */
    public Object createBean(Class<?> tClass,String beanName) throws IllegalAccessException, InstantiationException {
    //反射创建对象
    Object newInstance = tClass.newInstance();
    //实例化完就放到二级缓存
    earlySingletonObjects.put(beanName,newInstance);
    //开始填充属性
    populateBean(newInstance);
    //填充完成后从创作中的集合转移到完全体集合
    earlySingletonObjects.remove(beanName);
    singletonObject.put(beanName,newInstance);
    return newInstance;
    }

/**

  • 填充属性
    */
    public void populateBean(Object object) throws InstantiationException, IllegalAccessException {
    //获取所有添加了 @MyAutowired 注解的属性
    List autowiredFields = getAutowiredField(object.getClass());
    for (Field field : autowiredFields) {
    //开始注入
    doPopulateBean(object, field);
    }
    }

/**

  • 开始注入对象
  • @param object
  • @param field
    */
    public void doPopulateBean(Object object, Field field) throws IllegalAccessException, InstantiationException {
    //重新调用获取逻辑
    Object target = getBean(field.getType());
    field.setAccessible(true);
    //反射注入
    field.set(object,target);
    }

/**

  • 获取被标识自动注入的属性
  • @param tClass
  • @return
    /
    private List getAutowiredField(Class<?> tClass){
    Field[] declaredFields = tClass.getDeclaredFields();
    return Arrays.stream(declaredFields).filter(field ->
    ield.isAnnotationPresent(MyAutowired.class)).collect(Collectors.toList());
    }
    /
    *
  • 获取类名称
  • @param tClass
  • @return
    */
    public String getBeanName(Class<?> tClass){
    return tClass.getSimpleName();
    }
    }

结果

由上面的结果图示,我们解决了循环依赖,事实上Spring的解决方案,和我们手写的类似,但是Spring作为一个生态,它的设计和编码也是考虑的极其周全的,我们这样写虽然和Spring的最初想法是类似的,但是会出现哪些问题呢?

五、自己实现的方式有什么缺陷?

我们现在是直接注入类的对象,假设我们换了一种逻辑,如果我们注入的目标对象,是一个需要被代理的对象(比如该方法被AOP代理),我们这种写法就无能为力了,当然我们可以再创建的时候进行判断是否需要增加代理,当然这是一种方案,但是对于Spring而言,他的初衷是希望在bean生命周期的最后几步才去aop,再注入的时候就把该对象的代理逻辑给做完了,很显然不符合它的设计理念,那么Spring到底是如何解决的呢?

六、Spring中是如何解决循环依赖的?

首先,我们需要找到类在哪里实例化的,因为只有实例化了,才会执行注入的逻辑!

入口方法:

org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

@Override
public void preInstantiateSingletons() throws BeansException {
//遍历一个副本以允许使用init方法,这些方法依次注册新的bean定义。
//尽管这可能不是常规工厂引导程序的一部分,但可以正常运行。
//这里获取所有的bean name
List beanNames = new ArrayList<>(this.beanDefinitionNames);

// 触发所有非惰性单例bean的初始化…
for (String beanName : beanNames) {
//获取该类的详细定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//实例化条件 第一不是抽象的类 第二是单例的类 第三不是懒加载的类
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
//哦吼 这里引申出来一个概念 当这个bean集成了beanname那么就不再走bean生命周期的实例化了 直接创建
if (isFactoryBean(beanName)) {
…忽略不必要代码,正常bean的初始化不会走这里…
} else {
//普通的bean 这里就是再创建Spring bean的实体对象的,这里也是我们探究最重要的一个逻辑
getBean(beanName);
}
}
}
…忽略忽略…
}

这一步主要就是getBean,你试想一下,按照Spring的命名规范,这里明明是在创建Bean为什么就起个名字叫getBean呢?他这么做肯定是有他的用意所在,他这么起名是因为,在属性注入的时候,发现依赖某一个属性并不会立即创建,而是会调用这个方法获取一遍,没有再去创建!不明白没关系,你记住这个地方,往下看!方法进入到getBean –> doGetBean

protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

final String beanName = transformedBeanName(name);
Object bean;

// 检查单例缓存是否有手动注册的单例。
//检查一级缓存内是否有该单例bean的对象
//当一级缓存没有 而却当前的bean为创建中的状态时(实例化完成但是没有初始化),检查二级缓存对象,有就返回
//当二级缓存没有 检查三级缓存,调用三级缓存的匿名内部类的回调方法获取bean对象,放置到二级缓存,删除三级缓存的该数据 返回当前bean
//从三级缓存取的原因是因为如果该类为依赖类,并且被设置了代理,则再该方法内部获取的就是代理对象,保证注入时,第一次获取的就是一个代理对象
//事实上 如果是循环引用,被引用对象再注入属性时三级缓存已经存在,就会使用三级缓存的工厂对象,返回该bean该做代理的时候做代理,没代理的话直接返回
Object sharedInstance = getSingleton(beanName);
//当发生循环依赖时,第一次查询该对象返回的数据一定为null
if (sharedInstance != null && args == null) {
…忽略不必要代码…
} else {
…忽略不必要代码…
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
…忽略不必要代码,这里主要做一些判断,比如实例化时的依赖(@dependsOn)等…

// 创建bean实例。这个是个真正的创建bean实例的方法 单例池获取,没有的话就将该bean加入到正在创建 然后走创建bean的回调
if (mbd.isSingleton()) {
//这个方法很重要,方法内部会做这样几件事:
//1.判断当前的一级缓存里面有没有bean
//2.没有就回调java8里面的回调方法(createBean)创建方法,添加到一级缓存,返回bean
//3.一级缓存存在就直接返回该bean
sharedInstance = getSingleton(beanName, () -> {
try {
//这里是真正的创建bean的逻辑,由 {@link #getSingleton} 方法回调该对象去走真正的执行创建bean的逻辑
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// 从单例缓存中显式删除实例:它可能已经放在那里
// 急于通过创建过程,以允许循环引用解析。
// 还删除所有收到对该bean的临时引用的bean。
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
…忽略不必要代码…
} else {
…忽略不必要代码…
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}

if (requiredType != null && !requiredType.isInstance(bean)) {
…忽略不必要代码…
}
return (T) bean;
}

  • 之前手写了一遍解决循环依赖的代码,这里是不是很熟悉?这就是在缓存里面寻找对应的bean,当缓存有的时候直接返回,没有的时候才去创建!相信聪明的你一定若有所思!  这里极其重要,咱们进入到createBean里面

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
…忽略不必要代码…
try {
//真正干活的方法来了 呵呵呵呵 反射创建bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
//…忽略不必要代码…
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
//先前检测到的具有正确的bean创建上下文的异常,
//或非法的单例状态,最多可以传达给DefaultSingletonBeanRegistry。
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, “Unexpected exception during bean creation”, ex);
}
}

  • 进入到doCreateBean里面

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {

// 实例化bean。
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//开始创建bean 的逻辑 这里实际上该类已经被实例化了 只不过返回的是一个包装对象,包装对象内部存在该实例化好的对象
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//获取之前创建的bean
final Object bean = instanceWrapper.getWrappedInstance();

…忽略不必要代码…

//判断当前这个对象是不是单例 是不是支持循环引用 是不是正在创建中 满足这几个条件才会放置到三级缓存
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
…忽略不必要代码…

//这个方法时将当前实例号的bean放置到三级缓存 三级缓存内部存放的时 beanName -> bean包装对象 这个样的kv键值对
//设置这个方法的目的时 Spring设计时是期望Spring再bean实例化之后去做代理对象的操作,而不是再创建的时候就判断是否 是代理对象
//但实际上如果发生了循环引用的话,被依赖的类就会被提前创建出来,并且注入到目标类中,为了保证注入的是一个实际的代理对象
//所以Spring来了个偷天换日,偷梁换柱
//后续需要注入的时候,只需要通过工厂方法返回数据就可以了,在工厂里面可以做代理相关的操作,执行完代理操作后,在返回对象
//符合了Spring设计时,为了保证代理对象的包装再Springbean生命周期的后几步来实现的预期
//这一步还会删除二级缓存的数据
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// 初始化bean实例。
Object exposedObject = bean;
try {
//填充内部的属性
//☆这一步解决了循环依赖的问题,在这里发生了自动注入的逻辑
populateBean(beanName, mbd, instanceWrapper);
//执行初始化的逻辑 以及生命周期的回调
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
…忽略不必要代码…
}

if (earlySingletonExposure) {
…忽略不必要代码…
}
…忽略不必要代码…
return exposedObject;
}

  • 进入到populateBean 方法,这里执行属性注入,同时也解决了循环依赖!

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
…忽略不必要代码…

// 给任何InstantiationAwareBeanPostProcessors修改机会,
// 设置属性之前Bean的状态。例如,可以使用它
// 支持场注入方式。
boolean continueWithPropertyPopulation = true;

if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
…忽略不必要代码…
}
}

if (!continueWithPropertyPopulation) {
return;
}
…忽略不必要代码…
if (hasInstAwareBpps) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
//因为是使用@Autowired注解做的自动注入
// 故而Spring会使用 AutowiredAnnotationBeanPostProcessor.postProcessProperties来处理自动注入
//事实上这一步是会做注入处理的,这个也是我们重点观察的对象
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
…忽略不必要代码…
}

如何快速更新自己的技术积累?

  • 在现有的项目里,深挖技术,比如用到netty可以把相关底层代码和要点都看起来。
  • 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
  • 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
  • 学习以后不知道有没有学成,则可以通过面试去检验。

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目

八年CRUD,疫情备战三个月,三面头条、四面阿里拿offer面经分享

八年CRUD,疫情备战三个月,三面头条、四面阿里拿offer面经分享

底层代码和要点都看起来。

  • 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
  • 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
  • 学习以后不知道有没有学成,则可以通过面试去检验。

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目

[外链图片转存中…(img-KCuoKsPv-1714197666873)]

[外链图片转存中…(img-lzDAEWyZ-1714197666874)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值