首页 > 编程知识 正文

面试是双向选择,企业没有选择我(面试过了hr都不主动联系你)

时间:2023-05-04 17:09:53 阅读:84722 作者:4663

来自:开源中国,作者:想哭蜜粉丶

链接: https://my.oschina.net /张雪峰/博客/3096394

在有关Spring的面试中,经常会有人问Spring如何解决循环依赖的问题。 这个问题是关于Spring的高频率面试问题。 因为如果不刻意研究,即使读源代码,面试者也不一定能马上想到中神秘。 本文主要针对这个问题从源代码的角度说明其实现原理

1、过程演示

springbean的创建本质上也是对象的创建。 既然是对象,读者朋友必须明白,完整的对象包括两部分:当前对象的实例化和对象属性的实例化。 在Spring中,对象的实例化是通过反射来实现的,对象的属性在对象实例化后以固定的方式进行设置。 这个过程可以理解如下。

明白了这一点,对循环依赖的理解就迈出了一大步。 下面以两个“a”和“b”类为例进行说明。 以下是a和b的声明。

@Component

公共类a

私有B;

公共语音设置(bb ) {

this.b=b;

}

}

@Component

公共类b

私有A;

公共语音集(aa ) {

国际航空运输协会=a;

}

在此,我们可以看到a和b分别将对方作为自己的全局属性。 首先,请注意Spring实例化bean是由ApplicationContext.getBean () )方法完成的。 如果要获取的对象依赖于另一个对象,则首先创建当前对象,然后递归调用ApplicationContext.getBean ()方法获取依赖的对象,最后获取的对象是当前对象

下面以上面第一个初始化a对象实例为例进行说明。 首先,Spring尝试通过ApplicationContext.getBean ) )方法获取a对象的实例。 由于Spring容器中还没有a对象的实例,因此可以看到创建了a对象,然后依赖于b对象,递归地应用程序上下文. get bean ) ) )。 读者需要注意这个时候。 此时,已经创建了a对象和b对象,并将其保存在Spring容器中。 但是,尚未设置a对象的属性b和b对象的属性a。 在前一个Spring中创建了b对象之后,Spring发现b对象依赖于属性a,所以应该递归调用ApplicationContext.getBean )方法来获取a对象的实例这是因为Spring已经有一个a对象的实例。 但是,它还是半成品,未初始化属性b,但也是目标bean。此时,将设置b对象的属性a,并递归返回ApplicationContext.getBean ) )方法。 也就是说,将返回b对象的实例,并将该实例设置为a对象的属性b。 此时,请注意,a对象的属性b和b对象的属性a都设置了目标对象的实例。

读者们可能会怀疑的是,首先在对象b中设定属性a时,这种类型a的属性还是半成品。 但是,请注意,此a是引用,本质上是第一个实例化的a对象。 在上述递归过程的最后,Spring将获取的b对象的实例设置为a对象的属性b。 这里的a对象实际上是与前面在实例b中设置的半成品的a对象相同的对象,其引用目标相同。 在此,为a对象的b属性设置了值。 也就是说,为该半成品的a属性设定了值。 用流程图说明一下这个过程吧。

表示图中的getBean (是调用Spring的ApplicationContext.getBean )方法,该方法的参数表示要检索的目标对象。 图中的黑色箭头指示第一个方法调用的方向,表示一直走到最后,返回到Spring缓存的a对象后,递归调用返回,此时用绿色箭头表示。 如图所示,b对象的a属性是在步骤3中注入的半成品的a对象,a对象的b属性是在步骤2中注入的成品的b对象,半成品的a对象也是成品的a对象。 因为属性已经设定了。

关于Spring如何处理

2、源码讲解

循环依赖问题,这里通过上面的流程图可以简单地理解。 需要注意的一点是,如何标记Spring开始生成的a对象是一半

成品,并且是如何保存A对象的。这里的标记工作Spring是使用ApplicationContext的属性SetsingletonsCurrentlyInCreation来保存的,而半成品的A对象则是通过Map<string, objectfactory> singletonFactories来保存的,这里的ObjectFactory是一个工厂对象,可通过调用其getObject()方法来获取目标对象。在AbstractBeanFactory.doGetBean()方法中获取对象的方法如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // 尝试通过bean名称获取目标bean对象,比如这里的A对象 Object sharedInstance = getSingleton(beanName); // 我们这里的目标对象都是单例的 if (mbd.isSingleton()) { // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建 // 目标对象 sharedInstance = getSingleton(beanName, () -> { try { // 尝试创建目标对象 return createBean(beanName, mbd, args); } catch (BeansException ex) { throw ex; } }); } return (T) bean; }

这里的doGetBean()方法是非常关键的一个方法(中间省略了其他代码),上面也主要有两个步骤,第一个步骤的getSingleton()方法的作用是尝试从缓存中获取目标对象,如果没有获取到,则尝试获取半成品的目标对象;如果第一个步骤没有获取到目标对象的实例,那么就进入第二个步骤,第二个步骤的getSingleton()方法的作用是尝试创建目标对象,并且为该对象注入其所依赖的属性。

这里其实就是主干逻辑,我们前面图中已经标明,在整个过程中会调用三次doGetBean()方法,第一次调用的时候会尝试获取A对象实例,此时走的是第一个getSingleton()方法,由于没有已经创建的A对象的成品或半成品,因而这里得到的是null,然后就会调用第二个getSingleton()方法,创建A对象的实例,然后递归的调用doGetBean()方法,尝试获取B对象的实例以注入到A对象中,此时由于Spring容器中也没有B对象的成品或半成品,因而还是会走到第二个getSingleton()方法,在该方法中创建B对象的实例,创建完成之后,尝试获取其所依赖的A的实例作为其属性,因而还是会递归的调用doGetBean()方法,此时需要注意的是,在前面由于已经有了一个半成品的A对象的实例,因而这个时候,再尝试获取A对象的实例的时候,会走第一个getSingleton()方法,在该方法中会得到一个半成品的A对象的实例。然后将该实例返回,并且将其注入到B对象的属性a中,此时B对象实例化完成。然后将实例化完成的B对象递归的返回,此时就会将该实例注入到A对象中,这样就得到了一个成品的A对象。我们这里可以阅读上面的第一个getSingleton()方法:

@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回 Object singletonObject = this.singletonObjects.get(beanName); // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象 // 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的 // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 获取目标对象的实例 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }

这里我们会存在一个问题就是A的半成品实例是如何实例化的,然后是如何将其封装为一个ObjectFactory类型的对象,并且将其放到上面的singletonFactories属性中的。这主要是在前面的第二个getSingleton()方法中,其最终会通过其传入的第二个参数,从而调用createBean()方法,该方法的最终调用是委托给了另一个doCreateBean()方法进行的,这里面有如下一段代码:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的 BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } // 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是 // 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是 // 目标bean直接返回 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } try { // 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了, // 就会递归的调用getBean()方法尝试获取目标bean populateBean(beanName, mbd, instanceWrapper); } catch (Throwable ex) { // 省略... } return exposedObject; }

到这里,Spring整个解决循环依赖问题的实现思路已经比较清楚了。对于整体过程,读者朋友只要理解两点:

Spring是通过递归的方式获取目标bean及其所依赖的bean的;Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。

结合这两点,也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。

对了,在这里说一下,我目前是在职Java开发,如果你现在正在学习Java,了解Java,渴望成为一名合格的Java开发工程师,在入门学习Java的过程当中缺乏基础入门的视频教程,可以关注并私信我:01。获取。我这里有最新的Java基础全套视频教程。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。