【框架源码】Spring源码解析之Bean创建源码流程

这篇具有很好参考价值的文章主要介绍了【框架源码】Spring源码解析之Bean创建源码流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【框架源码】Spring源码解析之Bean创建源码流程

问题:Spring中是如何初始化单例bean的?

我们都知道Spring解析xml文件描述成BeanDefinition,解析BeanDefinition最后创建Bean将Bean放入单例池中,那么Spring在创建Bean的这个过程都做了什么。

Spring核心方法refresh()中最最重要的一个方法 finishBeanFactoryInitialization() 方法,该方法负责初始化所有的单例bean。

finishBeanFactoryInitialization()方法位于refresh()中第11步。

【框架源码】Spring源码解析之Bean创建源码流程

走到这一步的时候,Spring容器中所有的BeanFactory都已经实例化完成了,也就是实现BeanFactoryPostProcessor接口的 Bean 都已经初始化完成了。剩下的就是初始化singleton beans,在我们的业务bean中大多数都是单例的,finishBeanFactoryInitialization这一步就是去实例化单例的并且没有设置懒加载的Bean。

Spring会在finishBeanFactoryInitialization这个方法里面初始化所有的singleton bean

Ok,我们先来看一下finishBeanFactoryInitialization方法内部的逻辑。这个方法的核心就在于完成BeanFactory的配置。该阶段完成了上下文的实例化,包含所有单例Bean对象已经实例化。

【框架源码】Spring源码解析之Bean创建源码流程

核心在于 preInstantiateSingletons() 方法,preInstantiateSingletons方法主要任务是进行初始化,在初始化前同样是一系列判断,如,是否是懒加载的,是否是一个factorybean(一个特别的bean,负责工厂创建的bean),最后调用getBean()方法。
【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程

注释中提到的SmartInitializingSingleton接口,是让bean初始化后做一些操作。
【框架源码】Spring源码解析之Bean创建源码流程
【框架源码】Spring源码解析之Bean创建源码流程

OK,那么主要的方法还是getBean()方法,getBean()方法的作用就是加载、实例化Bean。方法内部调用了doGetBean(),我们直接看**doGetBean()**方法内部。
【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程

代码很多,我们主要看createBean()方法。
【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程

我们继续往doCreateBean 这个方法里面看。
【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程

方法很多,我们主需要关注三个方法即可。

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  • populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  • initializeBean:调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

什么是Spring的三级缓存?

Spring的IOC容器里面的三级缓存都是Map结构。

  • 一级缓存(成熟的bean)
    • singletonObjects 单例池 ,存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
  • 二级缓存
    • earlySingletonObjects 提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
  • 三级缓存
    • singletonFactories 单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖

【框架源码】Spring源码解析之Bean创建源码流程

OK,了解完三级缓存我们再来看下 getSingleton() 这个方法。这个方法主要是用于从单例池中获取指定名称的单例Bean实例,方法内部实现了三级缓存查找机制,通过三级查找的机制来获取指定名称的单例Bean实例对象,同时该方法会使用同步代码块保证多线程环境下的线程安全性。

【框架源码】Spring源码解析之Bean创建源码流程

OK,那么到这里我们会有一个疑问,Spring为什么要用三级缓存来解决循环依赖的问题。

首先我们要明确一点,Spring可以解决setter的依赖注入,但是不能解决构造器的依赖注入。

假如我们现在有个A对象B对象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对象完成了初始化。

【框架源码】Spring源码解析之Bean创建源码流程

知道了这个原理时候,肯定就知道为啥Spring不能解决"A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象"这类问题啦!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

下面一个案例带大家体验下构造器注入和set注入的演示

编写类A类B

public class A {
    private B b;
    public A() {}
    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
    public void method(){
        System.out.println("A方法调用");
    }
}
public class B {
    private A a;
    public B(){}
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
    public void method(){
        System.out.println("B方法调用");
    }
}

编写xml

    <bean id="b" class="com.lixiang.demo.B">
        <property name="a" ref="a"></property>
    </bean>
    <bean id="a" class="com.lixiang.demo.A">
        <property name="b" ref="b"></property>
    </bean>

测试

【框架源码】Spring源码解析之Bean创建源码流程

修改xml配置

    <bean id="b" class="com.lixiang.demo.B">
        <!--将a改成用构造器注入-->
        <constructor-arg name="a" ref="a"></constructor-arg>
    </bean>
    <bean id="a" class="com.lixiang.demo.A">
        <property name="b" ref="b"></property>
    </bean>

代码调整

public class B {
    private A a;
    public B(A a){
        this.a = a;
    }
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
    public void method(){
        System.out.println("B方法调用");
    }
}

测试

【框架源码】Spring源码解析之Bean创建源码流程

可以发现用构造器注入是发生异常的。

Spring引入了“提前暴露Bean”的机制,在创建A对象时,会先创建一个A的空对象并将其添加到缓存池中。

即“提前暴露Bean”,然后继续创建B对象,将其注入A对象中。在创建B对象时,由于A对象已经在缓存池中,可以直接获取到A对象,接着将B对象注入到A对象中,完成Bean的初始化。

【框架源码】Spring源码解析之Bean创建源码流程

好的,到现在整一个Bean的创建流程,就已经完成啦。我们在看一下以下三个方法的具体实现。

首先第一个就是 createBeanInstance() 方法
【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程

然后是 populateBean() 方法
【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程

最后是 initializeBean() 方法
【框架源码】Spring源码解析之Bean创建源码流程【框架源码】Spring源码解析之Bean创建源码流程

ok,到这里Spring的bean的创建过程就已经梳理完成啦。

记得点个赞+关注哦!

【框架源码】Spring源码解析之Bean创建源码流程文章来源地址https://www.toymoban.com/news/detail-485577.html

到了这里,关于【框架源码】Spring源码解析之Bean创建源码流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Spring】Spring之Bean生命周期源码解析

    什么是bean的生命周期 是指bean在spring中是如何生成,如何销毁的; spring创建对象的过程,就是IOC(控制反转)的过程; JFR Java Flight Record,java飞行记录,类似于飞机的黑匣子,是JVM内置的基于事件的JDK监控记录框架,主要用于问题定位和持续监控; 入口代码: Spring启动的时

    2024年02月15日
    浏览(36)
  • 【Spring 】执行流程解析:了解Bean的作用域及生命周期

     哈喽,哈喽,大家好~ 我是你们的老朋友: 保护小周ღ    今天给大家带来的是 Spring 项目的执行流程解析  和 Bean 对象的6 种作用域 以及 生命周期 , 本文将为大家讲解,一起来看看叭~ 本期收录于博主的专栏 :JavaEE_保护小周ღ的博客-CSDN博客 适用于编程初学者,感兴趣

    2024年02月16日
    浏览(30)
  • Spring之Bean生命周期源码解析

    ClassPathBeanDefinitionScanner.java ClassPathScanningCandidateComponentProvider.java 通过组件索引寻找 这里的 componentsIndex 在初始化的时候会尝试解析 META-INF/spring.components 文件中的配置信息 把断点打在 ClassPathScanningCandidateComponentProvider 的 setResourceLoader 方法上调试可以看到堆栈 可以看到,的确

    2024年02月11日
    浏览(37)
  • 【深入Spring源码解析:解密Bean的生命周期】

    Spring是Java企业级应用开发领域的一颗明星,它提供了很多方便开发人员的工具和思想。在分布式系统中,Spring的分布式远程协作方案,比如REST、Web服务以及消息传递等,也是不可或缺的。 你知道吗?在我们使用Spring时,容器中存放的所有对象,在Spring启动的时候就完成了实

    2024年02月05日
    浏览(33)
  • Spring源码解析(八):bean后置处理器CommonAnnotationBeanPostProcessor

    Spring源码系列文章 Spring源码解析(一):环境搭建 Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean Spring源码解析(三):bean容器的刷新 Spring源码解析(四):单例bean的创建流程 Spring源码解析(五):循环依赖 Spring源码解析(六):bean工厂后置处理器ConfigurationCla

    2024年02月13日
    浏览(35)
  • Spring源码之XML文件中Bean标签的解析1

    xml文件里包含Bean的信息,为了避免多次IO,需要一次性读取xml文件中所有bean信息,加入到Spring工厂。 读取配置文件 ClassPathResource是Spring封装的一个类型; Resource接口 :可以读取相关资源文件的内容 获得输入流;可读取的类型,不仅包括本地的xml、 properties、txt 等文件,还包

    2024年02月13日
    浏览(37)
  • 【Spring专题】Spring之Bean的生命周期源码解析——上(扫描生成BeanDefinition)

    由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序): Spring底层核心原理解析——引导篇【学习难度: ★★☆☆☆ 】

    2024年02月13日
    浏览(30)
  • 【Spring专题】Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)

    由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序): Spring底层核心原理解析——引导篇【学习难度: ★★☆☆☆ 】

    2024年02月13日
    浏览(32)
  • 【Spring专题】Spring之Bean的生命周期源码解析——阶段二(IOC之实例化)

    由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序): Spring底层核心原理解析——引导篇【学习难度: ★★☆☆☆ 】

    2024年02月13日
    浏览(43)
  • 4.是人就能学会的Spring源码教程-IOC容器创建Bean对象

    我们要关注一个接口 BeanFactory ,它是Spring IOC容器的根接口,也是容器的入口。 类的描述中已经清楚的说明了: 我们来看一下这个接口里面的方法。 我们可以看到有各种各样的 getBean 方法,让我们可以从容器中获取到各种各样的Bean对象。 BeanFactory 有一个实现类 DefaultListab

    2024年02月05日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包