Spring Bean 生命周期和Spring循环依赖面试及其源码讲解

在这里插入图片描述

      真快啊,转眼明天就要迎来了祖国母亲的生日,街头上到处五星红旗迎风飘扬,胜利歌声多么响亮,歌唱我们亲爱的祖国、、、、在这里我先提前预祝大家十一快乐,希望大家在回家探亲路上和游玩路上一定要注意安全,并且因为口罩问题,一定做好个人防护,好了,来自大家长的絮叨就到这吧,下面步入正题。
       本来没打算今天来分享这篇文章的,但是奈何今天9月30号,然后活昨天基本做完,今天大家还基本上请假了,做其他的感觉容易分心(我看大家的心已经跑到了九霄云外),那么我就专心给大家写文章分享知识吧。
       Spring Bean生命周期和Spring循环依赖的仔细学习和源码分析是上上周我记得就已经研究过了,本打算记笔记,这两周工作太忙,一直没有时间来复盘总结,正好趁着这个时间点并且也有一段时间没有给大家写过文章了,就来写篇文章给大家分享下吧。这篇文章刚开始准备分两篇分开来介绍Spring Bean 生命周期和Spring循环依赖的,但是想想他们两个都是在差不多的源码里,一起介绍感觉效果会比较好些,所以在写这篇文章前想还是和大家一篇文章一起来介绍吧。
       上面那么多废话像是许久不见的老友,相互寒暄寒暄,但是正题不能忘,下面开始正题。

      一、Spring Bean生命周期

         1.1、什么是IOC?

                  这问题我想大家应该都不会陌生,IOC是Spring非常重要的功能之一,IOC就是控制翻转嘛,我来先给大家来句我的理解,也就是大白话:以前我们没有使用Spring IOC的时候,创建对象就是直接使用new Student(),我们需要使用对象的时候,需要先new出来对象,然后才可以进行使用,但是现在我们交给了Spring IOC容器帮我们管理,那么Spring IOC容器会帮助我们来创建、管理和销毁。这就是我所理解的对其大白话描述出来,虽然不好看,但是自我感觉通俗易懂。
                  这里我来严肃介绍下,不使用大白话的。IOC就是控制反转,何为反转,就是把new 对象的权利交给Spring容器来进行管理,之前我们都是自己new对象,这样的我们可以称之为正传。
在这里插入图片描述

         1.2、Bean生命周期

                  这块我们所讲的Bean生命周期主要围绕的是单例Bean(Singleton Bean)来讲解,因为在多例Bean(Prototype)中,在getBean中获取到Prototype Bean后,会交由用户管理,IOC容器就不会对其再管理,再次getBean时,会再次创建新的Bean。
                  注意:下面我所讲解Bean全部是单例Bean,不是多例Bean。

Bean生命周期流程图如下所示:

在这里插入图片描述Bean生命周期主要过程

  • 实例化: 第1步:实例化一个对象
  • 属性赋值: 第2步:给第1步中实例化的对象属性赋值
  • 初始化: 初始化的阶段的步骤比较多,5、6 步是真正的初始化,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,初始化完成之后,Bean 就可以被使用了;
  • 使用: 这里就是已经完全实例化好的对象可以使用了。
  • 销毁: 第 8~10 步,第 8 步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第 9、10 步真正销毁 Bean 时再执行相应的方法。

         1.3、Bean生命周期执行流程

创建一个yglBean

public class YglBean implements InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean {

    /**
     * 姓名
     */
    private String name;

    public YglBean() {
        System.out.println("1.调用构造方法:我出生了!");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2.设置属性:我的名字叫"+name);
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("3.调用BeanNameAware#setBeanName方法:我要上学了,起了个学名");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("4.调用BeanFactoryAware#setBeanFactory方法:选好学校了");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("6.InitializingBean#afterPropertiesSet方法:入学登记");
    }

    public void init() {
        System.out.println("7.自定义init方法:努力上学ing");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("9.DisposableBean#destroy方法:平淡的一生落幕了");
    }

    public void destroyMethod() {
        System.out.println("10.自定义destroy方法:睡了,别想叫醒我");
    }

    public void work(){
        System.out.println("Bean使用中:工作,只有对社会没有用的人才放假。。");
    }
}

自定义一个后处理器 MyBeanPostProcessor。

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5.BeanPostProcessor.postProcessBeforeInitialization方法:到学校报名啦");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("8.BeanPostProcessor#postProcessAfterInitialization方法:终于毕业,拿到毕业证啦!");
        return bean;
    }
}

applicationContext.xml 配置文件(部分)。

<bean name="myBeanPostProcessor" class="demo.MyBeanPostProcessor" />
<bean name="yglBean" class="demo.YglBean"
      init-method="init" destroy-method="destroyMethod">
    <property name="name" value="ygl" />
</bean>

测试入口:

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context =new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        LouzaiBean louzaiBean = (LouzaiBean) context.getBean("yglBean");
        louzaiBean.work();
        ((ClassPathXmlApplicationContext) context).destroy();
    }
}

执行结果:

1.调用构造方法:我出生了!
2.设置属性:我的名字叫ygl
3.调用BeanNameAware#setBeanName方法:我要上学了,起了个学名
4.调用BeanFactoryAware#setBeanFactory方法:选好学校了
5.BeanPostProcessor.postProcessBeforeInitialization方法:到学校报名啦
6.InitializingBean#afterPropertiesSet方法:入学登记
7.自定义init方法:努力上学ing
8.BeanPostProcessor#postProcessAfterInitialization方法:终于毕业,拿到毕业证啦!
Bean使用中:工作,只有对社会没有用的人才放假。。
9.DisposableBean#destroy方法:平淡的一生落幕了
10.自定义destroy方法:睡了,别想叫醒我

这个流程非常清晰,Bean 生命周期流程图能完全对应起来。

         1.4、扩展方法

                  我们发现整个生命周期中用到了很多扩展过程,大致分为4类

  • Aware接口: 让 Bean 能拿到容器的一些资源,例如 BeanNameAware 的 setBeanName(),BeanFactoryAware 的 setBeanFactory()
  • 后置处理器: 进行一些前置和后置的处理,例如 BeanPostProcessor 的 postProcessBeforeInitialization()postProcessAfterInitialization()
  • 生命周期接口: 定义初始化方法和销毁方法的,例如 InitializingBean 的 afterPropertiesSet(),以及 DisposableBean 的 destroy()
  • 配置生命周期方法: 可以通过配置文件,自定义初始化和销毁方法,例如配置文件配置的 init()destroyMethod()
              生命周期的源码这里我先不讲解,我先介绍循环依赖,等下把Bean生命周期的源码和循环依赖源码一起来讲解。

      二、Spring 循环依赖

              2.1、什么是循环依赖

                  我还先用大白话来给大家介绍下什么是循环依赖:就是A需要干这件事时,需要B的帮助;然后B就去干,B在干的过程中发现干这件事需要A来帮助,然后这样就陷入循环尴尬场景了。
形成循环依赖有以下三种场景:
在这里插入图片描述我们看一个简单的 Demo,对标“情况 2”。

@Service
public class ClassA {

    @Autowired
    private ClassB classB;

    public void test1() {
    }
}

@Service
public class ClassB {
    @Autowired
    private ClassA classA;

    public void test2() {
    }
}

这是一个经典的循环依赖,它能正常运行,后面我们会通过源码的角度,解读整体的执行流程。

              2.2、Spring能帮助我们解决什么样的Bean循环依赖场景

  • 有参构造方法实例化Bean时Spring无法解决
  • 多例Bean时Spring无法解决
    注意: Spring只能帮助我们解决通过无参构造方法实例化单例Bean

              2.3、Spring三级缓存

  • 第一级缓存:singletonObjects,用于保存实例化,属性注入和初始化都已经完成的Bean实例。
  • 第二级缓存: earlySingletonObjects,已经实例化完成的Bean,还没有进行属性注入。也就是半成品Bean实例。
  • 第三级缓存: singletonFactories,Bean创建的工厂,以便后续Bean需要实现AOP创建代理对象。

在这里插入图片描述执行逻辑:

  • 会先去一级缓存中取,如果能够取到直接返回;为null的话,就会往下走
  • 到需要二级缓存中取之前有两个条件才可以去取:1、一级缓存中不存在;2、该Bean为正在创建状态。 二级缓存中取到直接返回,否则去三级缓存中拿
  • 三级缓存中拿到后返回,且该Bean从三级缓存中移除,存放至二级缓存中。

              2.4、循环依赖原理执行流程

                  下面就上面的情况2(A依赖B,B依赖A来说明下运行原理)
情况2的步骤分成下面3步,感觉就是各种套娃。原理流程如下所示:
在这里插入图片描述整个执行逻辑:
       1、在第一层中,先去获取 A 的 Bean,发现没有就准备去创建一个,然后将 A 的代理工厂放入“三级缓存”(这个 A 其实是一个半成品,还没有对里面的属性进行注入),但是 A 依赖 B 的创建,就必须先去创建 B;
       2、在第二层中,准备创建 B,发现 B 又依赖 A,需要先去创建 A;
       3、在第三层中,去创建 A,因为第一层已经创建了 A 的代理工厂,直接从“三级缓存”中拿到 A 的代理工厂,获取 A 的代理对象,放入“二级缓存”,并清除“三级缓存”
       4、回到第二层,现在有了 A 的代理对象,对 A 的依赖完美解决(这里的 A 仍然是个半成品),B 初始化成功
       5、回到第一层,现在 B 初始化成功,完成 A 对象的属性注入,然后再填充 A 的其它属性,以及 A 的其它步骤(包括 AOP),完成对 A 完整的初始化功能(这里的 A 才是完整的 Bean)。
       6、清除二级缓存,将 A 放入“一级缓存”
                    为什么要用 3 级缓存 ?我们先看源码执行流程,后面我会给出答案。

      三、Spring Bean生命周期和循环依赖源码解析

            这里之所以想把生命周期和循环依赖一起分析,不分开分析,很重要的原因就是我认为循环依赖是在Bean生命周期的属性注入时会发生循环依赖的;如果一起讲的话,大家对这方面理解更加透彻和明白。下面就开始在代码中翱翔吧,我先给大家来个Bean生命周期和循环依赖的流程图(只含有流程,不包含任何代码)吧。
在这里插入图片描述下面开始进入源码分析:
只招重点代码讲解,这里基本的流程在上面已经和大家分享过了哈。

在这里插入图片描述3.1、Bean实例化在这里插入图片描述在这里插入图片描述开始先获取Bean
Spring中真正实现业务逻辑的将方法开头都是do开头
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
注意在doCreate实例化完成之后需要将其塞入到三级缓存中去,这里存放的是BeanFactory.在这里插入图片描述在这里插入图片描述将对象实例化完成且放入到三级缓存中之后,将开始属性注入。

3.2、属性赋值
在这里插入图片描述加粗样式在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述

3.3、解决循环依赖
      在属性赋值时,发现需要Bean B,然后开始走getBean()方法获取,但是从单例池中获取失败,那么开始实例化Bean,实例化Bean的步骤和上面实例化Bean A一样的,同样实例化B之后会存放进三级缓存中,然后开始属性注入,属性注入时发现发现依赖Bean A,那么此时回去走getBean(A),然后从单例池中去获取,首先从一级缓存中获取,但是获取失败;当从一级缓存中获取失败,且该Bean时正在创建状态,那么会从二级缓存中获取;如果二级缓存中获取失败,那么从三级缓存中获取,由于上面Bean A 在实例化之后存放进了三级缓存,那么此时从三级缓存中获取到且返回,并且还有一步就是将三级缓存中获取到后会移除三级缓存,然后将Bean A存放进二级缓存,此时Bean A仍然是半成品。然后Bean B实例化完成,属性注入完成,此时可以初始化Bean B,初始化B之后,返回给Bean A,此时的A也已经实例化完成,属性注入完成,然后开始初始化,初始化完成之后,会将Bean A从三级缓存、二级缓存中删除,将Bean A存放进一级缓存中,因为此时Bean A已经给实例化完成。

3.4、初始化
在这里插入图片描述
在这里插入图片描述在这里插入图片描述走进示例 LouzaiBean 的方法,给 LouzaiBean 设置 BeanName。

在这里插入图片描述回到 invokeAwareMethods()。
在这里插入图片描述走进示例 LouzaiBean 的方法,给 LouzaiBean 设置 BeanFactory。

在这里插入图片描述第一次回到 initializeBean(),执行下面逻辑。

在这里插入图片描述这里需要多循环几次,找到 MyBeanPostProcessor 的策略方法。
在这里插入图片描述我们自己定义的后置处理方法。
在这里插入图片描述第二次回到 initializeBean(),执行下面逻辑。

在这里插入图片描述
在这里插入图片描述走进示例 LouzaiBean 的方法,执行 afterPropertiesSet()。
在这里插入图片描述返回 invokeInitMethods(),执行下面逻辑。
在这里插入图片描述进入 invokeCustomInitMethod(),执行下面逻辑。
在这里插入图片描述走进示例 LouzaiBean 的方法,执行 init()。

在这里插入图片描述第三次回到 initializeBean(),执行下面逻辑。
在这里插入图片描述
在这里插入图片描述我们自己定义的后置处理方法。
在这里插入图片描述到这里,初始化的流程全部结束,都是围绕 initializeBean() 展开。
3.5、Bean生命周期几个重要方法

  • doCreateBean(): 这个是入口;
  • createBeanInstance(): 用来初始化 Bean,里面会调用对象的构造方法;
  • populateBean(): 属性对象的依赖注入,以及成员变量初始化;
  • initializeBean(): 里面有 4 个方法,
    • 先执行 aware 的 BeanNameAware、BeanFactoryAware 接口;
    • 再执行 BeanPostProcessor 前置接口;
    • 然后执行 InitializingBean 接口,以及配置的 init();
    • 最后执行 BeanPostProcessor 的后置接口。
  • destory(): 先执行 DisposableBean 接口,再执行配置的 destroyMethod()。

      四、Spring 循环依赖相关方面问题

         4.1、为什么需要3级缓存

            我们先说 “一级缓存”的作用,变量命名为 singletonObjects,结构是 Map<String, Object>,它就是一个单例池,将初始化好的对象放到里面,给其它线程使用,如果没有第一级缓存,程序不能保证 Spring 的单例属性
            “二级缓存”先放放,我们直接看“三级缓存”的作用,变量命名为 singletonFactories结构是 Map<String, ObjectFactory<?>>,Map 的 Value 是一个对象的代理工厂,所以“三级缓存”的作用,其实就是用来存放对象的代理工厂
            那这个对象的代理工厂有什么作用呢,我先给出答案,它的主要作用是存放半成品的单例 Bean,目的是为了“打破循环”,可能大家还是不太懂,这里我再稍微解释一下。
            我们回到文章开头的例子,创建 A 对象时,会把实例化的 A 对象存入“三级缓存”,这个 A 其实是个半成品,因为没有完成 A 的依赖属性 B 的注入,所以后面当初始化 B 时,B 又要去找 A,这时就需要从“三级缓存”中拿到这个半成品的 A(这里描述,其实也不完全准确,因为不是直接拿,为了让大家好理解,我就先这样描述),打破循环。
            那我再问一个问题,为什么“三级缓存”不直接存半成品的 A,而是要存一个代理工厂呢 ?答案是因为 AOP。
            在解释这个问题前,我们看一下这个代理工厂的源码,让大家有一个更清晰的认识。
            直接找到创建 A 对象时,把实例化的 A 对象存入“三级缓存”的代码,直接用前面的两幅截图。
在这里插入图片描述

在这里插入图片描述下面我们主要看这个对象工厂是如何得到的,进入 getEarlyBeanReference() 方法。
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述最后一幅图太重要了,我们知道这个对象工厂的作用:

如果 A 有 AOP,就创建一个代理对象;

  • 如果 A 有 AOP,就创建一个代理对象;
  • 如果 A 没有 AOP,就返回原对象。
            那“二级缓存”的作用就清楚了,就是用来存放对象工厂生成的对象,这个对象可能是原对象,也可能是个代理对象。

         4.2、二级缓存能够去掉吗?

@Service
public class A {

    @Autowired
    private B b;

    @Autowired
    private C c;

    public void test1() {
    }
}

@Service
public class B {
    @Autowired
    private A a;

    public void test2() {
    }
}

@Service
public class C {

    @Autowired
    private A a;

    public void test3() {
    }
}

根据上面的套娃逻辑,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A。

假如 A 需要进行 AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:

  • B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
  • C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。
    **看到问题没?**你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。
            所以“二级缓存”的目的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。
            如果没有 AOP 的话,我们其实只要 1、3 级缓存,就可以满足要求。

         4.3、三级缓存作用

  • 一级缓存: 为“Spring 的单例属性”而生,就是个单例池,用来存放已经初始化完成的单例 Bean;
  • 二级缓存: 为“解决 AOP”而生,存放的是半成品的 AOP 的单例 Bean;
  • 三级缓存: 为“打破循环”而生,存放的是生成半成品单例 Bean 的工厂方法。

         好了上面就是基本的Spring Bean生命周期和循环依赖的源码和一些问题的解读,有哪些分享不对的知识点,还恳请大家在下面评论区中指出。非常感谢。
         最后祝福为我们的祖国庆生,祝福我们的祖国永远繁荣昌盛。
          大家记得一键三连哦,十一接着卷。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岭岭颖颖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值