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() 方法返回的类型。

相关文章
|
4天前
|
XML Java 数据格式
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
13 1
|
4天前
|
XML druid Java
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
8 0
|
5天前
|
Java Spring
Spring的BeanPostProcessor后置处理器与bean的生命周期
Spring的BeanPostProcessor后置处理器与bean的生命周期
|
3天前
|
消息中间件 安全 Java
学习认识Spring Boot Starter
在SpringBoot项目中,经常能够在pom文件中看到以spring-boot-starter-xx或xx-spring-boot-starter命名的一些依赖。例如:spring-boot-starter-web、spring-boot-starter-security、spring-boot-starter-data-jpa、mybatis-spring-boot-starter等等。
17 4
|
4天前
|
XML 安全 Java
Spring 基础知识学习
Spring 基础知识学习
|
4天前
|
Java Spring 容器
Spring5系列学习文章分享---第六篇(框架新功能系列+整合日志+ @Nullable注解 + JUnit5整合)
Spring5系列学习文章分享---第六篇(框架新功能系列+整合日志+ @Nullable注解 + JUnit5整合)
5 0
|
4天前
|
XML Java 数据库
Spring5系列学习文章分享---第五篇(事务概念+特性+案例+注解声明式事务管理+参数详解 )
Spring5系列学习文章分享---第五篇(事务概念+特性+案例+注解声明式事务管理+参数详解 )
6 0
|
4天前
|
SQL Java 数据库连接
Spring5系列学习文章分享---第四篇(JdbcTemplate+概念配置+增删改查数据+批量操作 )
Spring5系列学习文章分享---第四篇(JdbcTemplate+概念配置+增删改查数据+批量操作 )
6 0
|
4天前
|
XML Java 数据格式
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
7 0
|
4天前
|
Java Spring
Spring注解内容----用来替代Bean
Spring注解内容----用来替代Bean