Spring之Bean生命周期源码分析(一)

1,512 阅读10分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情

一、前言

这是我Spring专栏的第六篇文章: Spring之Bean生命周期源码分析(一), 主要讲解了Bean声明周期中的 包扫描, Bean实例对象生成的前置流程. 在看本篇文章之前建议先看一下上篇文章当做前置学习 Spring之概念和工作流程 在之前我为大家讲解了以下内容:

二、生成、合成BeanDefinition

前置信息

老规矩, 直接通过创建Spring容器的构造方法进去, 可以看到它先执行了无参构造, 在点进去, 在AnnotationConfigApplicationContext() 方法中第一行和第三行不用看, 那个是和JRF相关的, 可以忽略掉

StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
...
createAnnotatedBeanDefReader.end();

image.png

this.reader = new AnnotatedBeanDefinitionReader(this); 这边就不细讲了, 具体可以插看我的上一篇文章Spring之概念和工作流程

还是上面的方法, 直接进入 refresh() 刷新方法

image.png

进入该方法的详情

image.png

在该方法下面找到这行代码, 点进去

image.png

里面有一个方法, 是用来实例化非懒加载的单例Bean的

image.png

在该方法中, 创建单例Bean的流程大致如下:

  • 取出所有的BeanNameList
  • 遍历 BeanNameList
  • 判断 (不是抽象的BeanDefinition, 是单例, 非懒加载)
  • 生成 Bean对象

抽象的BeanDefinition可通过目录快速跳转到 抽象的 BeanDefinition

image.png

生成BeanDefinition-Spring包扫描

Spring启动的时候会进行扫描, 会先调用
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents(String basePackage) 扫描某个包路径, 并得到BeanDefinition集合

首先我们看方法, 可以看到入参就是 包扫描地址, 里面调用了一个 doScan(backPackages) 方法, 这个方法就是具体的获取扫描信息的 // 方法地址 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan(String... basePackages)

image.png

根据包路径扫描获取 BeanDefinition

doScan(backPackages)方法 在源码里可以看到, 这边有个方法的返回值就是 BeanDefinition 集合, 那么这个就是核心的存储 Bean的容器了, 我们可以在点进去看看里面具体的实现

image.png

findCandidateComponents(basePackage)方法 在这个方法里面去做了响应的判断, 上面那个判断在本标题的最后进行简单的讲解, 大部分情况都是else的, 红框部分的方法是相对比较核心的的, 接着往下走

image.png

具体的上面的if语句判断可通过目录快捷指向到标题 findCandidateComponents方法的判断

包扫描方法详情

scanCandidateComponents(basePackage) 方法 我把源码在这里贴一下, 一张图截不下, 我放两张, 左侧也有相应的代码行数

image.png image.png

第424行是获取basePackage下所有的文件资源, packageSearchPath是获取一个地址, classpath*:扫描包路径/**/*.class 具体如下图所示

image.png

获取该路劲路径下的资源文件(.class文件)数组

getResourcePatternResolver().getResources(packageSearchPath); 该方法的作用是获取该路劲路径下的资源文件(.class文件)数组

在往下走, 他会对获取到的所有文件进行一个遍历, 途中红框部分是Spring中的一个元数据读取器, 在上篇文章: Spring之概念和工作流程中标题八有讲到

元数据读取器可以获取到当前注解的信息, 类的名字, 实现的接口, 父类等, 底层用的ASM技术

获取到元数据之后, 我们会对当前类进行判断, 该类是在排除过滤器中还是在包含过滤器

排除过滤器和包含过滤器在上一篇文章标题九中有讲到 Spring之概念和工作流程

  • 注意: 下图中new ScannedGenericBeanDefinition(metadataReader)的注释标注有误, 实际是设置BeanClass的名字

image.png

进入这个方法就可以看到其对排除和包含过滤器的判断, 我们主要看第三个红框, 因为我们可以看到, 如果该方法想要返回true只能看第三个框中的方法

image.png

我们在该方法中也是一直点, 进入具体的实现方法中, 可以看到下图所示代码

image.png

解释一下上面代码中红框部分的判断

// metadata是类上的注解信息
// 如果该类没有注解或者该类上没有 Conditional注解
// 返回FALSE代表不要跳过, 表示它就是一个Bean
if (metadata == null || !metadata.isAnnotated( Conditional.class.getName())) {
   return false;
}

通过以上判断确定该类是Bean之后, 通过以下代码设置 Bean的Class名字同时添加 Bean的resource

ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);

// 上面方法具体实现如下:
    Assert.notNull(metadataReader, "MetadataReader must not be null");
    this.metadata = metadataReader.getAnnotationMetadata();
    // 这里只是把className设置到BeanDefinition中
    setBeanClassName(this.metadata.getClassName());
    setResource(metadataReader.getResource());

添加完成之后, 进入以下判断, 大概判断的是: 是不是内部类, 接口, 抽象类

image.png

doScan方法内部代码流程

到这里, 我们获取 Set<BeanDefinition> 的具体实现方法就讲解完成了, 我们回到 doScan()方法 接着往下进行

image.png

关于Bean名字的生成流程, 具体代码各种跳转就不贴了:

  • 判断该类上的注解 @Component 的 value值
  • 若该值存在, 则返回
  • 若该值不存在, 则调用方法生成
    • 若类名前两个字符都是大写字符则直接返回
    • 否则将第一个字符小写返回

检查Spring容器中是否已经存在当前 beanName

上图中红框部分接口: 检查Spring容器中是否已存在当前BeanName, 具体方法如下图所示:

image.png

findCandidateComponents方法的判断

image.png 上图这个判断主要是对 BeanDefinition的生成做一个快捷方式扫描 Bean的方式, 具体方式如下图所示

判断哪些类是由 @Component注解的, 具体源代码就不贴了, 很少用到

image.png

抽象的 BeanDefinition

通过以下方法设置的 BeanDefinition就是抽象的

image.png

引出下面的父子BeanDefinition

二、实例化非懒加载的单例Bean

首先进入 实例化非懒加载的单例Bean的方法中, 下图中爆红是因为那个方法太长了, 我临时删除了内部一些代码导致的

image.png

在这个方法里面, 一共有两个for循环遍历 beanNames, 第一个 for循环会生成所有的非懒加载单例Bean

image.png

第一次for循环流程说明:

  • 根据 beanName 获取 合并后的 BeanDefinition
  • 判断 BeanDefinition 是不是抽象, 单例, 懒加载
  • 判断当前Bean是不是 FactoryBean, 不管是不是都会去创建 Bean对象

第二次for循环流程说明:

  • 根据 beanName 找出对应的的单例对象
  • 判断单例对象是否实现了 SmartInitializingSingleton接口
  • 若实现了, 执行 smartSingleton.afterSingletonsInstantiated(); (后面有讲, 可根据目录快速跳转)

我们回到文章开篇创建Bean的那个方法里面, 我们可以看到它内部做了非常多的操作, 接下来我会为大家讲解一些比较核心重要的执行步骤

image.png

获取 RootBeanDefinition

获取合并之后的 BeanDefinition

image.png

找到当前 BeanDefinition

如果通过当前 beanName能在合并map(mergedBeanDefinitions)中取到 BeanDefinition则返回

image.png

没找到当前 BeanDefinition

假设当前 beanName 不是 合并BeanDefinition 继续往下走

image.png

最后我们到了这个方法里面, 注意: 这里参数 containingBd 为null

由于 containingBd 为null , 我们会直接执行下面这个方法, 又因为我们进到这个方法, 就是因为当前 beanName 不是 合并BeanDefinition, 所以 mbd = null, 继续往下执行到红框位置

if (containingBd == null) {
   mbd = this.mergedBeanDefinitions.get(beanName);
}

当前 BeanDefinition不存在 父BeanDefinition

在红框位置去判断 当前bean有没有父bean, 如果没有, 则判断当前 BeanDefinition有没有实例化 RootBeanDefinition, 然后去生成一个新的 RootBeanDefinition, 不管有没有实例化都生成

父子BeanDefinition具体可以看标题三, 如下图所示

image.png

最后会直接进入这个方法, 将当前 BeanDefinition放入 mergedBeanDefinitions 中

image.png

当前 BeanDefinition存在 父BeanDefinition

流程如下:

  • 获取当前 BeanDefinition 的 父BeanDefinition名字
  • 判断当前 beanName是否等于 parentBeanName
    • 不相等:
      • 递归 继续进行合并
    • 相等:
      • 获取 父BeanFactory
      • 如果 父Bean工厂 实现了 ConfigurableBeanFactory 接口则 递归合并
      • 若未实现则抛出异常

image.png

FactoryBean

这边会通过 beanName去判断当前 bean是不是一个 FactoryBean, 具体实现如下图所示

在 isFactoryBean(String name) 方法中, 第一行代码的降价在下面 FactoryBean标题下面

image.png

上图中, 我特意用红框圈出来一个方法, 这个方法的作用是: 通过 beanName去单例池中取数据, 但是这个时候是获取不到的, 因为我们还没有去生成 bean对象, 所以继续往下走

在下面代码中, 去做判断, 如果当前 beanName去取不到 BeanDefinition, 就去判断它的 父BeanDefinition有没有实现 FactoryBean

if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
   return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
}

最后, 拿到合并之后的 BeanDefinition, 在往下走, 去判断 BeanDefinition的类型, 在判断是不是一个 FactoryBean, 在返回

image.png

获取FactoryBean对象

获取FactoryBean对象靠的是 getBean(String name)方法

image.png

我们可以看到, 这个方法的入参是在我们的 beanName前面增加了一个符号 &

我们进入这个方法内部去看一下

image.png

这里我们看到了熟悉的代码, 这行代码会根据你传入的 name不同而有所变化

String beanName = transformedBeanName(name);

image.png

如上图所示, 可以看到, 我们在 实例化非懒加载单例Bean的时候, 有三个地方调用到了 getBean(String name)方法, 且第一次调用在 beanName前加了符号 &

在 getBean(String name)中, 会通过 transformedBeanName 方法去获取 beanName

然后根据获取到的 beanName去单例池中查找 获取单例对象, 如果获取到了单例对象, 那么会根据 bean的单例对象, beanName和传进来的 name去获取 bean实例

如果没有获取到了单例对象, 则会继续进行 bean的生命周期, 最后生成 bean的单例对象, 具体可以看我的下一篇Spring文章

创建 Bean对象

创建Bean对象就要用到 getBean(String beanName)方法了, 对于 getBean(String beanName) 方法的源码解析, 将作为下篇Spring系列文章发布在我的: Spring专栏

getBean 方法就是去创建一个Bean对象, 之前我还简单的手写模拟过整个过程, 感兴趣的可以去看一下Spring之手写模拟Spring Bean的创建

实现SmartInitializingSingleton接口

我们来实现这个接口, 会自动生成一个方法

image.png

通过上面的分析, 可以知道, 这个方法的执行时间是: 所有单例Bean都实例化之后

因为是在所有Bean实例化之后才去判断当前的Bean有没有实现这个接口

image.png

三、合成BeanDefinition-父子BeanDefinition

生成父子 BeanDefinition代码如下

<bean id="juejin1" class="com.ningxuan.service.Juejin" scope="prototype"/>

<bean id="juejin2" class="com.ningxuan.service.Juejin" parent="juejin1"/>

image.png

因为child的父BeanDefinition是parent,所以会继承parent上所定义的scope属性。

而在根据child来生成Bean对象之前,需要进行BeanDefinition的合并,得到完整的child的BeanDefinition。

本文内容到此结束了

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

我是 宁轩 , 我们下次再见