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

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

一、前言

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

二、生成、合成BeanDefinition

前置信息

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

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

网络异常,图片无法展示
|

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

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

网络异常,图片无法展示
|

进入该方法的详情

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

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

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

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

网络异常,图片无法展示
|

生成BeanDefinition-Spring包扫描

Spring启动的时候会进行扫描, 会先调用

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents(String basePackage)扫描某个包路径, 并得到BeanDefinition集合

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

网络异常,图片无法展示
|

根据包路径扫描获取 BeanDefinition

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

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

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

包扫描方法详情

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

网络异常,图片无法展示
|
网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

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

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

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

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

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

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

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

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

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

// 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());
复制代码

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

网络异常,图片无法展示
|

doScan方法内部代码流程

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

网络异常,图片无法展示
|

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

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

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

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

网络异常,图片无法展示
|

findCandidateComponents方法的判断

网络异常,图片无法展示
|
上图这个判断主要是对 BeanDefinition的生成做一个快捷方式扫描 Bean的方式, 具体方式如下图所示

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

网络异常,图片无法展示
|

抽象的 BeanDefinition

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

网络异常,图片无法展示
|

引出下面的父子BeanDefinition

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

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

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

第一次for循环流程说明:

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

第二次for循环流程说明:

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

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

网络异常,图片无法展示
|

获取 RootBeanDefinition

获取合并之后的 BeanDefinition

网络异常,图片无法展示
|

找到当前 BeanDefinition

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

网络异常,图片无法展示
|

没找到当前 BeanDefinition

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

网络异常,图片无法展示
|

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

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

if (containingBd == null) {
   mbd = this.mergedBeanDefinitions.get(beanName);
}
复制代码


目录
相关文章
|
1天前
|
Java 应用服务中间件 Spring
Spring系列文章:Bean的作⽤域
Spring系列文章:Bean的作⽤域
|
1天前
|
XML 前端开发 Java
【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)
【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)
4 0
|
1天前
|
存储 缓存 Java
【JavaEE】Spring中注解的方式去获取Bean对象
【JavaEE】Spring中注解的方式去获取Bean对象
2 0
|
1天前
|
存储 Java 对象存储
【JavaEE】Spring中注解的方式去存储Bean对象
【JavaEE】Spring中注解的方式去存储Bean对象
5 0
|
1天前
|
存储 Java 对象存储
【JavaEE】DI与DL的介绍-Spring项目的创建-Bean对象的存储与获取
【JavaEE】DI与DL的介绍-Spring项目的创建-Bean对象的存储与获取
9 0
|
1天前
|
安全 Java Spring
Spring框架中的单例Bean是线程安全的吗?
Spring框架中的单例Bean是线程安全的吗?
10 1
|
1天前
|
消息中间件 安全 Java
在Spring Bean中,如何通过Java配置类定义Bean?
【4月更文挑战第30天】在Spring Bean中,如何通过Java配置类定义Bean?
21 1
|
1天前
|
XML Java 数据格式
Spring Bean
【4月更文挑战第30天】Spring Bean
17 0
|
1天前
|
前端开发 Java 数据格式
【Spring系列笔记】定义Bean的方式
在Spring Boot应用程序中,定义Bean是非常常见的操作,它是构建应用程序的基础。Spring Boot提供了多种方式来定义Bean,每种方式都有其适用的场景和优势。
32 2
|
1天前
|
XML Java 数据格式
谈谈 Spring 中 Bean 的生命周期?
谈谈 Spring 中 Bean 的生命周期?
20 1