热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Spring源码解读】Spring的依赖注入

依赖注入大致流程◆Spring在Bean实例的创建过程中做了很多精细化控制finishBeanFactoryInitialization(beanFactory);Insta

依赖注入大致流程

◆Spring在Bean实例的创建过程中做了 很多精细化控制

finishBeanFactoryInitialization(beanFactory);
// Instantiate all remaining (non-lazy-init) singletons.beanFactory.preInstantiateSingletons();

finishBeanFactoryInitialization(beanFactory);方法里面的preInstantiateSingletons()会去初始化那些非延时加载的bean实例。

而ApplicationConCtext容器默认管理的bean都是非延迟的单例,所以如果没有特别的设置,bean都会在这里被调用getBean方法去实例化。


image-20220109173010988
image-20220109173519694

AbstractAutowiredCapableBeanFactory:自动装配的服务

image-20220109174708512

AbstractBeanFactory: 抽象类,里面提供一个获取bean实例的方法doGetBean,根据不同的scope进行不同的处理

DefaultSingletonRegistry: 单例bean默认的实现类,里面是bean而不是beanDefinition,里面有一个方法会尝试从三级缓存里获得bean实例,(三级缓存是为了解决循环依赖的问题,循环依赖:类A里面套类B,类B又有A的情况)

如果缓存里没有bean实例,那么容器就会创建出来,因此就会来到AbstractAutowiredCapableBeanFactory,在doCreateBean方法里面光创建Bean实例是不够的,还需要看看实例的属性有没有被@Autowired或者@Value标记。

applyMergeBeanDefinitionPostProcessors: 对合并后的BeanDifination做后置处理

populateBean: 考虑被标记之后,对属性进行依赖注入

AutowiredAnnotationBeanPostProcessor: 做属性的后置处理,再来到DefaultListableBeanFactory这里去解析依赖关系(因为BeanDefinition都保存在这里),找到bean之间的依赖关系之后使用DependencyDescriptor里面的injectionPoint进行注入


doGetBean

image-20220109200055178

1、单例Bean有可能是bean实例(直接返回),或者是FactoryBean实例本身(当调用getObject方法去返回bean实例),获取不到就让容器去创建bean

什么是循环依赖?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qkOeO5YR-1642509457647)(../../../../Users/大 大/AppData/Roaming/Typora/typora-user-images/image-20220109200641031.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FEaonCbk-1642509457649)(../../../../Users/大 大/AppData/Roaming/Typora/typora-user-images/image-20220109200655756.png)]

3、如果该容器中没有注册该beandefinition的实例,则递归去父容器获取Bean实例。

5、所谓的显式就是给bean设置deoends-on这个属性。

image-20220109201244816

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
//**从缓存中获取bean实例**

从缓存中获取bean实例

image-20220109201618567

image-20220109201845624

完备的bean实例会被保存在一级缓存singletonObjects,不完备的bean实例被保存在二级缓存earlySingletonObjects或者三级缓存singletonFactories里,并且实例只能存在于三层中的某一层。

为什么没有注入属性的bean还呢个让其返回呢?

为了解决循环依赖的问题,打破鸡生蛋,蛋生鸡的无限轮回。

isSingletonCurrentlyInCreation();//存放当前正在创建的bean的名字列表

isSynthetic:表示某个类是由自己机制生成的类,

bean.isSynthetic():表示该bean是spring容器内部生成的bean实例,不允许第三方或者用户去改动。

bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);//如果是普通的bean直接返回,如果是FactoryBean,则返回它的getObject

为什么我们都可以通过 getSingleton(beanName); 获得实例了,为什么还要有getObjectForBeanInstance?

原因是看看bean是否是FactoryBean,如果是还需要调用它的getObject()方法去返回实例。

spring为什么不显式指定依赖?

因为既然你显示指定了依赖,就表明某bean一定是在另一个bean之前创建,depends-on主要是用来控制顺序的。如果产生了循环依赖,spring也不知道用户究竟先要那个bean先创建出来。

dependentBeanMap:我所依赖的那些bean的名字的那些列表

UserInfo类里面有一个Human类的成员变量,则userInfo是依赖human的,所以human就作为dependentBeanMap里面的key,userInfo则为dependentBeanMap值的里面的其中一个元素。

dependenciesForBeanMap:依赖以我的bean的名字的列表

另外一边dependenciesForBeanMap关系和上面就是反过来的。它的key存储的是userInfo,human作为值的其中的一个元素存储里面。

image-20220110141144766

两个getSingleton方法。第一个:尝试从三级缓存里获取需要创建的bean实例。

image-20220110142529040

另外一个调用getObject方法最终调用createBean的方法去真正创建出bean实例来。

image-20220110142617845

image-20220110142633986

只有单例是支持循环依赖的


createBean

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactoryimplements AutowireCapableBeanFactory {

里面的createBean方法

@Override
public Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException {// Use non-singleton bean definition, to avoid registering bean as dependent bean.RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck);bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);return createBean(beanClass.getName(), bd, null);
}

主要去了解单例的@Autowired的依赖注入

image-20220110143402364

主要解析bean对应的class对象

之前往容器里创建了BeanDefinition实例,创建bean之前会取出BeanDefinition实例,之后依据BeanDefinition的属性创建出bean实例来。

RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck);

为什么使用RootBeanDefinition接收BeanDefinition实例?

因为有的bean有parent属性,将child和parent属性合并后归并到chile里面,而如果是普通的bean,RootBeanDefinition也可以接收。

提前暴露:bean是单例并且支持循环依赖的同时还满足正在创建的条件。满足上面三个条件就允许将创建出来的但是还没有赋值上属性的bean给暴露在外供外部使用。


image-20220110145703918
image-20220110145638121

Suppier接口里面的通过get方法返回设置上的bean实例,该接口用来暂存实例,以供匿名调用。


createBean过程:

覆盖方法

public void prepareMethodOverrides() throws BeanDefinitionValidationException {// Check that lookup methods exist and determine their overloaded status.if (hasMethodOverrides()) {getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);}
}

image-20220110163420625

主要就是通过beanDefinition实例加载class对象,获取到class对象之后再看看BeanDifinition里面是否配置了replace-method或者lookup属性,进而检查一下是否有相关覆盖的方法,并做相关的标记。随后就回去判断是否要对bean进行包装处理,如果没有就调用doCreateBean方法进行spring容器对bean的创建。


doCreateBean过程


首先会通过实现配置好的工厂方法、含参构造器注入、无参构造器注入创建没有属性值的bean实例。之后执行BeanDefinition的后置处理器来处理BeanDefinition的属性,其中就包含了被@Autowired和@Value标记的属性,先将被这些标签标记的属性给记录下来,放到一个类里面,便于后序的依赖注入处理。之后会看看是否允许早期暴露(Bean是单例并且是支持循环依赖的同时还满足正在创建的条件)。之后填充Bean属性例如@Autowired属性就是在这注入的,之后对bean进行初始化操作,包括通过责任链模式用BeanPostProcessor去处理bean,对bean做最后的包装。初始化之后就会视情况给创建好的bean实例注册bean销毁时的相关逻辑,之后将创建好的bean返回给调用方调用。


调试代码断点处

image-20220110170229518

getEarlyBeanReference

实际执行的地方:在AbstractBeanFactory的doGetBean方法里尝试获取缓存中的bean(即getSingleton方法)调用里面的getObject方法来执行bean实例的包装逻辑,


完备Bean的创建过程:


getBean方法被执行时会调用doGetBean方法,它先尝试从缓存中获取bean实例(getSingleton),如果缓存中没有,doGetBean方法会去调用createBean方法,在调用里面的doCreateBean方法进行创建bean实例。doCreateBean方法执行之后会将ObjectFactory这个实例给注册到三级缓存,在调用GetBean时就会来到缓存里去调用singletonFactory.getObject()(也就是getEarlyBeanReference开始执行)获取bean实例,再将实例存到二级缓存里,清空三级缓存实例,将完备的bean实例逐层返回。最后返回到doGetBean方法里,调用getSingleton方法(不同于上面那个)里的addSingleton,将返回的实例添加到一级缓存里,将完备bean二级、三级缓存给移除。



单例循环依赖解决:


例子:

@Repository
public class GirlFriend {@Autowiredprivate BoyFriend boyFriend;
}

@Repository
public class BoyFriend {@Autowiredprivate GirlFriend girlFriend;
}

解决路线:

在这里插入图片描述


当初此创建A实例的时候,会调用addSingletonFactory将A实例添加到三级缓存里,随后调用populateBean给A实例赋值属性B实例,赋值时发现B实例不存在。因此去调用getBean方法去获取B实例,B实例也一样的,先将B实例添加到三级缓存里,在调用populateBean方法给B里面的属性A赋值,此时就会再次调用getBean()方法获取A实例,就可以从缓存中获得A实例给B注入上,完成了B的创建,随后将B的完备实例放入到一级缓存,并清空其他级缓存之后,将其完备实例返回给A,这样A也就完成了创建,后将A也放入一级缓存中,并清空其他级缓存,进而就完成了两个相互依赖的A、B单例的创建。



Spring是否支持所有循环依赖的情况

循环依赖的情况如下


◆构造器循环依赖( singleton、prototype(不支持循环依赖) )
◆Setter注入循环依赖( singleton、prototype )



prototype(多例)构造函数循环依赖注入演示:

构造器注入需要给构造器添加@Autowired标签

image-20220111114010165

image-20220111114152478

主函数里面:

容器在初始化的时候就加载了bean实例,为什么还要显示调用getBean方法?

refresh里面是初始化,容器只是在刷新完成之后提前加载非延时加载的单例,所以对于prototype来讲,需要显示调用进行首次加载。

在这里插入图片描述

image-20220111114525926

image-20220111114655495

发现直接抛异常,说明spring不支持prototype循环依赖的情况。


set的方式的prototype循环依赖注入:

image-20220111114904245

image-20220111114959768

主函数

image-20220111115026319

抛出异常

为什么spring不支持多例的循环依赖情况呢?

点进去getBean方法

image-20220111115259073


当bean是prototype的并且显示在正在创建的bean的prototype列表当中,如果此处能够从缓存当中获得相关的内容,则表明getBean方法发生了递归,(因为prototypesCurrentlyInCreation是ThreadLocal的,如果同一个线程在执行时能在缓存中取得同样的bean,则表明getBean方法发生了递归,因为第一次执行是没有的,之后会像缓存中存储创建的bean的名字,再次执行到这里发现缓存是有值的,即是发生了递归的),那么如果在prototype正在创建的bean名字列表里找到该名字,则直接抛出异常


解决循环依赖的关键就是三级缓存,而三级缓存还解决了保证单例唯一性的问题。因为从缓存中取出来的实例是要保证bean是唯一的,所以三级缓存支持不了prototype。这也是prototype没有使用三级缓存而是简单的将bean的名字放到缓存当中的原因

image-20220111120208815


image-20220111120452197

单例构造器循环依赖的情况

image-20220111120609938

image-20220111120636009

主函数:

image-20220111120701171

发现抛出异常

说明spring容器下singleton下只支持setter方式的循环注入

为什么单例的构造器方式不支持循环依赖呢?


单例的bean在首次创建时会来到doCreateBean方法里,对于setter的创建回去到addSingletonFactory的方法里面,先将创建出来的还未赋值的不完备的bean存放在三级缓存里,之后在调用populateBean方法递归调用给其属性赋值注入。而对于构造器来说就不会等到populateBean的时候才注入,而是在前面的createBeanInstance方法里创建出来的,此时还没有缓存,此时构造器的参数实例并没有被创建出来,即company里面的staff实例还没有被创建出来,则在这里又会尝试创建staff实例,而staff的构造函数有需要company实例,它没有被放入到缓存中,又会递归来到这里,造成无限循环,spring会提前把正在创建的单例的名字列表给缓存起来,以避免出现无限循环。


image-20220111121244666

image-20220111121219620

image-20220111121422455

image-20220111122007702


populateBean

先前已经通过反射获得了一个bean,现在是往bean里面填充属性的时候。

◆调用Bean的Setter方法实例去给Bean设置上属性值

◆变量类型的转换,同时还要考虑处理集合类型的情况(在spring当中所有属性都是字符串,)

◆处理显式自动装配的逻辑( autowire = byName/byType )

image-20220111141552881

第二步主要针对xml的情况,在变量上显式定义了( autowire = byName/byType )

通过容器的刷新获取非延时加载的bean的单单例的实例。

容器里面是否已经注册了自定义的InstantiationAwareBeanPostProcessor的bean处理器。如果有则通过责任链模式调用这些后置处理器的postProcessAfterInstantiation方法。


本章小结

image-20220111154655887

本章重点分析了spring依赖注入的脉络,以容器初始化的时候调用getBean()方法,创建非延时加载的bean单例为契入点,先是从AbstractBeanFactory的doGetBean开始

三级缓存:解决了循环依赖注入问题以及保证bean单例唯一性的问题

一级缓存保存了完备的带有属性值得bean实例,二级缓存没有给属性赋值得bean实例,三级缓存保存了用来包装实例得Object实例,

image-20220111155126683

doGetBean方法的主要逻辑:

先从缓存中尝试获取Bean,没获取到再去判断循环依赖,之后递归去父容器获取Bean实例,此时如果容器没有配置父容器或者当前容器已经找到了对应BeanDefinition,那么就会从当前容器获取BeanDefinition实例,之后递归实例化显式(bean标签里被depends-on配置)依赖得Bean(一旦标签里配置了bean A depends-on B,那么就会将两者相互依赖的关系注册到容器里面,并且去实例化bean A所依赖得bean B),再根据不同得Scope策略创建Bean实例,我们主要研究得是Singleton,最后对Bean进行类型检查。

doCreateBean方法的主要逻辑:

如果是第一次创建Bean实例得话,最终会去调用createBean方法,为Bean实例得创建去做准备。随后调用doCreateBean方法进行真正的创建(主要针对的是setter方法注入的情况)

image-20220110143402364


image-20220111162122965

首先调用bean对应得参构造方法,创建出不带属性值得bean实例。针对注解来讲会调用applyMeragePostProccessor方法将被@Autowired或者@Value标记得方法和成员变量保存到injectionMeragedata实例中,并将injectionMeragedata实例保存到容器缓存里。随后判断容器中得bean是否允许提前暴露(即将没创建完备得bean实例给提前存入到缓存里以供递归方法通过getBean方法去获取,为了解决循环依赖的问题),之后调用populateBean方法对bean属性进行填充,之后对Bean进行初始化的处理,之后注册相关销毁逻辑,最后返回创建好的实例。

populateBean方法得逻辑

image-20220111161224148

首先postProcessAfterInstantiation给用户提供绕开spring自行处理bean属性的口子,如果口子没有被使用再继续后续操作。先判断是否显示装配了自动配置,如果有则byType或者byName进行自动装配。如果没有自动装配标记则来到postProcessPropertyValues,对前面存到缓存得Bean对应得injectionMeragedata实例通过inject方法对被@@Autowired或者@Value标记得Bean属性进行处理。之后进行依赖检查校验,随后如果是xml方式且先前指定了byType或者byName进行自动装配,就会执行最后面的逻辑,来给bean的属性进行赋值,

image-20220111162039899

Bean级别的后置处理器,作用:方便在调用getBean方法去创建Bean实例时,在特定的各个环节去触发这些后置处理器,以执行一些特定得逻辑,比如AOP的织入等等,以便对bean进行精细化的操作以及自定义的管理。


推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 标题: ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • C++语言入门:数组的基本知识和应用领域
    本文介绍了C++语言的基本知识和应用领域,包括C++语言与Python语言的区别、C++语言的结构化特点、关键字和控制语句的使用、运算符的种类和表达式的灵活性、各种数据类型的运算以及指针概念的引入。同时,还探讨了C++语言在代码效率方面的优势和与汇编语言的比较。对于想要学习C++语言的初学者来说,本文提供了一个简洁而全面的入门指南。 ... [详细]
author-avatar
甜心菇
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有