likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 74

一、前言

二、生成、合成BeanDefinition

前置信息

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

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

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

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

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

进入该方法的详情

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

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

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

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

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

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

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

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

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

生成BeanDefinition-Spring包扫描

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

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

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

根据包路径扫描获取 BeanDefinition

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

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

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

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

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

包扫描方法详情

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 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());

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

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

doScan方法内部代码流程

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

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

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

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

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

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

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

findCandidateComponents方法的判断

Spring之Bean生命周期源码分析(一) 上图这个判断主要是对 BeanDefinition的生成做一个快捷方式扫描 Bean的方式, 具体方式如下图所示

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

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

抽象的 BeanDefinition

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

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

引出下面的父子BeanDefinition

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

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

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

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

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

第一次for循环流程说明:

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

第二次for循环流程说明:

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

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

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

获取 RootBeanDefinition

获取合并之后的 BeanDefinition

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

找到当前 BeanDefinition

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

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

没找到当前 BeanDefinition

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

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

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

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

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

当前 BeanDefinition不存在 父BeanDefinition

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

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

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

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

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

当前 BeanDefinition存在 父BeanDefinition

流程如下:

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

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

FactoryBean

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

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

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

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

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

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

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

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

获取FactoryBean对象

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

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

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

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

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

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

String beanName = transformedBeanName(name);

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

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

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

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

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

创建 Bean对象

实现SmartInitializingSingleton接口

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

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

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

三、合成BeanDefinition-父子BeanDefinition

生成父子 BeanDefinition代码如下

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

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

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

本文内容到此结束了

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

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

我是 宁轩 , 我们下次再见

转载自:https://juejin.cn/post/7135955206134562823
评论
请登录