Spring 源码学习(四) bean 的加载(上)

简介: 既然我们 Spring 辛辛苦苦将 bean 进行了注册,当然需要拿出来进行使用,在使用之前还需要经过一个步骤,就是 bean 的加载。在第一篇笔记提到了,完成 bean 注册到 beanDefinitionMap 注册表后,还调用了很多后处理器的方法,其中有一个方法 finishBeanFactoryInitialization(),注释上面写着 Instantiateall remaining(non-lazy-init)singletons,意味着非延迟加载的类,将在这一步实例化,完成类的加载。而我们使用到 context.getBean("beanName")方法,如果对应的

下面一起来看下这两个步骤中, bean 是如何进行加载的。

  • 前言
  • 时序图
  • 代码分析
  • FactoryBean 的使用
  • 从缓存中获取单例 bean
  • 从 bean 的实例中获取对象
  • 获取单例
  • 准备创建 bean
  • 处理 Override 属性
  • 实例化前的前置处理
  • 创建 bean
  • 创建 bean 的实例
  • 处理循环依赖
  • 属性注入
  • 初始化 bean
  • 注册 disposableBean
  • 总结
  • 参考资料

时序图

我们的代码分析都是围绕着这个方法,请同学们提前定位好位置:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

这个 bean 加载的代码量是有点多的,已经超过 100 行,所以整理了时序图,希望能对加载流程有个清晰的概览:

43.jpg

这个时序图介绍了 bean 加载的大体流程,还有很多细节没在图中进行展示。我们先对整体流程有个了解,然后跟着代码一起深入分析吧。


代码分析

再提示一下:由于代码量很多,每次贴大段代码看起来会比较吃力,所以展示的是我认为的关键代码,下载项目看完整注释,跟着源码一起分析~

码云 Gitee 地址

Github 地址


FactoryBean 的使用

在分析加载流程之前,有个前置概念要了解下,在一般情况下, Spring 是通过反射机制利用 beanclass 属性指定实现类来实例化 bean

引用书本:

在某些情况下,实例化 bean 比较复杂,例如有多个参数的情况下,传统方式需要在配置文件中,写很多配置信息,显得不太灵活。在这种情况下,可以使用 Spring 提供的 FactoryBean 接口,用户可以通过实现该接口定制实例化 bean 的逻辑。

FactoryBean 接口定义了三个方法:

  1. {
  2.    T getObject()throwsException;
  3.    Class<?> getObjectType();
  4.    defaultboolean isSingleton(){
  5.        returntrue;
  6.    }
  7. }

主要讲下用法吧:

当配置文件中的 <bean>class 属性实现类是 FactoryBean 时,通过 getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法返回的对象。

使用 demo 代码请看下图:

44.jpg

扩展 FactoryBean 之后,需要重载图中的两个方法,通过泛型约定返回的类型。在重载的方法中,进行自己个性化的处理。

在启动类 Demo,通过上下文获取类的方法 context.getBean("beanName"),使用区别是 beanName 是否使用 & 前缀,如果有没有 & 前缀,识别到的是 FactoryBean.getObject 返回的 car 类型,如果带上 & 前缀,那么将会返回 FactoryBean 类型的类。

验证和学习书中的概念,最快的方式是运行一遍示例代码,看输出结果是否符合预期,所以参考书中的例子,自己手打代码,看最后的输出结果,发现与书中说的一致,同时也加深了对 FactoryBean 的了解。


为什么要先讲 BeanFactory 这个概念呢?

从时序图看,在 1.5 那个步骤,调用了方法:

org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance

在这一步中,会判断 sharedInstance 类型,如果属于 FactoryBean,将会调用用户自定义 FactoryBeangetObject() 方法进行 bean 初始化。

实例化的真正类型是 getObjectType() 方法定义的类型,不是 FactoryBean 原来本身的类型。最终在容器中注册的是 getObject() 返回的 bean

提前讲了这个概念,希望大家在最后一步时不会对这个有所迷惑。


从缓存中获取单例 bean

  1. // Eagerly check singleton cache for manually registered singletons.
  2. // 检查缓存中或者实例工厂是否有对应的实例或者从 singletonFactories 中的 ObjectFactory 中获取
  3. Object sharedInstance = getSingleton(beanName);

  4. protectedObject getSingleton(String beanName,boolean allowEarlyReference){
  5.    Object singletonObject =this.singletonObjects.get(beanName);
  6.    // 检查缓存中是否存在实例
  7.    if(singletonObject ==null&& isSingletonCurrentlyInCreation(beanName)){
  8.        // 记住,公共变量都需要加锁操作,避免多线程并发修改
  9.        synchronized(this.singletonObjects){
  10.            // 如果此 bean 正在加载则不处理
  11.            singletonObject =this.earlySingletonObjects.get(beanName);
  12.            if(singletonObject ==null&& allowEarlyReference){
  13.                // 当某些方法需要提前初始化,调用 addSingletonFactory 方法将对应的
  14.                // objectFactory 初始化策略存储在 singletonFactories
  15.                ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);
  16.                if(singletonFactory !=null){
  17.                    singletonObject = singletonFactory.getObject();
  18.                    this.earlySingletonObjects.put(beanName, singletonObject);
  19.                    this.singletonFactories.remove(beanName);
  20.                }
  21.            }
  22.        }
  23.    }
  24.    return singletonObject;
  25. }

单例模式在代码设计中经常用到,在 Spring 中,同一个容器的单例只会被创建一次,后续再获取 bean 直接从单例缓存 singletonObjects 中进行获取。

而且因为单例缓存是公共变量,所以对它进行操作的时候,都进行了加锁操作,避免了多线程并发修改或读取的覆盖操作。

还有这里有个 earlySingletonObjects 变量,它也是单例缓存,也是用来保存 beanName 和 创建 bean 实例之间的关系。

singletonFactories 不同的是,当一个单例 bean 被放入到这 early 单例缓存后,就要从 singletonFactories 中移除,两者是互斥的,主要用来解决循环依赖的问题。(循环依赖下一篇再详细讲吧)


从 bean 的实例中获取对象

getBean 方法中, getObjectForBeanInstance 是个高频方法,在单例缓存中获得 bean 还是 根据不同 scope 策略加载 bean,都有这个方法的出现,所以结合刚才说的 BeanFactory 概念,一起来看下这个方法做了什么。

org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance

  1. // 返回对应的实例,有时候存在诸如 BeanFactory 的情况并不是直接返回实例本身
  2. // 而是返回指定方法返回的实例
  3. bean = getObjectForBeanInstance(sharedInstance, name, beanName,null);

具体方法实现,搜索 注释 4.6 看代码中的注释吧:

45.jpg

交代一下这个方法的流程:

  • 验证 bean 类型:判断是否是工厂 bean
  • 对非 FactoryBean 不做处理
  • bean 进行转换
  • 处理 FactoryBean 类型:委托给 getObjectFromFactoryBean 方法进行处理。

在这个方法中,对工厂 bean 有特殊处理,处理方法跟上面提到的 FactoryBean 使用一样,最终获取的是 FactoryBean.getObject() 方法返回的类型。

相关文章
|
13天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
4天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
3天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
23 9
|
23天前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
33 9
|
22天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
24天前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
19 1
|
29天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
28天前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
52 2
|
28天前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
49 1
|
28天前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
19 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
下一篇
无影云桌面