一、前言
这是我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方法的判断
判断哪些类是由 @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); } 复制代码