2021-05-27 23:42  阅读(241)
文章分类:Spring 源码解析 文章标签:SpringSpring 源码
©  原文作者:Java学习部落 原文地址:https://www.zhihu.com/people/tobetopjavaer/posts

首先来看一张Bean的解析过程图【需要高清大图可以找我哦】

202105272342438831.png

依赖注入发生的时间

当 Spring IOC 容器完成了 Bean 定义资源的定位、载入和解析注册以后,IOC 容器中已经管理类 Bean定义的相关数据,但是此时 IOC 容器还没有对所管理的 Bean 进行依赖注入,依赖注入在以下两种情况发生:

1)、用户第一次调用 getBean()方法时,IOC 容器触发依赖注入。

2)、当用户在配置文件中将元素配置了 lazy-init=false 属性,即让容器在解析注册 Bean 定义时进行预实例化,触发依赖注入。

BeanFactory 接口定义了 Spring IOC 容器的基本功能规范,是 Spring IOC 容器所应遵守的最底层和最基本的编程规范。

BeanFactory 接口中定义了几个 getBean()方法,就是用户向 IOC 容器索取管理的Bean 的方法,我们通过分析其子类的具体实现,理解 Spring IOC 容器在用户索取 Bean 时如何完成依赖注入。

202105272342443973.png

在 BeanFactory 中我们可以看到 getBean(String...)方法,但它具体实现在AbstractBeanFactory 中。

寻找获取 Bean 的入口

AbstractBeanFactory 的 getBean()相关方法的源码如下:

202105272342447155.png202105272342447967.png202105272342453679.png2021052723424589411.png2021052723424621413.png2021052723424659615.png

通过上面对向 IOC 容器获取 Bean 方法的分析,我们可以看到在 Spring 中,如果 Bean 定义的单例模式(Singleton),则容器在创建之前先从缓存中查找,以确保整个容器中只存在一个实例对象。

如果 Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。

除此之外,Bean 定义还可以扩展为指定其生命周期范围。

上面的源码只是定义了根据 Bean 定义的模式,采取的不同创建 Bean 实例对象的策略,具体的Bean实例 对象的创 建过程 由实现了ObjectFactory接口的匿名内部类的 createBean()方法 完成,ObjectFactory 使 用 委 派 模 式 , 具 体 的 Bean 实 例 创 建 过 程 交 由 其 实 现 类 AbstractAutowireCapableBeanFactory 完成,我们继续分析AbstractAutowireCapableBeanFactory的 createBean()方法的源码,理解其创建 Bean 实例的具体实现过程。

开始实例化

AbstractAutowireCapableBeanFactory 类实现了 ObjectFactory 接口,创建容器指定的 Bean 实例对象,同时还对创建的 Bean 实例对象进行初始化处理。其创建 Bean 实例对象的方法源码如下:

2021052723424696817.png2021052723424734219.png2021052723424785221.png2021052723424840823.png2021052723424891725.png

通过上面的源码注释,我们看到具体的依赖注入实现其实就在以下两个方法中:

1)、createBeanInstance()方法,生成 Bean 所包含的 java 对象实例。

2)、populateBean()方法,对 Bean 属性的依赖注入进行处理。

下面继续分析这两个方法的代码实现。

选择 Bean 实例化策略

在 createBeanInstance()方法中,根据指定的初始化策略,使用简单工厂、工厂方法或者容器的自动装配特性生成 Java 实例对象,创建对象的源码如下:

2021052723424914627.png2021052723424980829.png2021052723425039731.png

经过对上面的代码分析,我们可以看出,对使用工厂方法和自动装配特性的 Bean 的实例化相当比较清楚,调用相应的工厂方法或者参数匹配的构造方法即可完成实例化对象的工作,但是对于我们最常使用的默认无参构造方法就需要使用相应的初始化策略(JDK 的反射机制或者 CGLib)来进行初始化了,在方法 getInstantiationStrategy().instantiate()中就具体实现类使用初始策略实例化对象。

执行 Bean 实例化

在使用默认的无参构造方法创建 Bean 的实例化对象时,方法getInstantiationStrategy().instantiate()调用了 SimpleInstantiationStrategy 类中的实例化 Bean 的方法,其源码如下:

2021052723425080833.png2021052723425127635.png

通过上面的代码分析,我们看到了如果 Bean 有方法被覆盖了,则使用 JDK 的反射机制进行实例化,否则,使用 CGLib 进行实例化。

instantiateWithMethodInjection()方法用SimpleInstantiationStrategy的子类 CGLibSubclassingInstantiationStrategy 使用 CGLib 来进行初始化,其源码如下:

2021052723425169237.png2021052723425209039.png

CGLib 是一个常用的字节码生成器的类库,它提供了一系列 API 实现 Java 字节码的生成和转换功能。

我们在学习 JDK 的动态代理时都知道,JDK 的动态代理只能针对接口,如果一个类没有实现任何接口,要对其进行动态代理只能使用 CGLib。

准备依赖注入

在前面的分析中我们已经了解到 Bean 的依赖注入主要分为两个步骤,首先调用 createBeanInstance()方法生成 Bean 所包含的 Java 对象实例。然后,调用 populateBean()方法,对 Bean 属性的依赖注入进行处理。

上面我们已经分析了容器初始化生成 Bean 所包含的 Java 实例对象的过程,现在我们继续分析生成对象后,Spring IOC 容器是如何将 Bean 的属性依赖关系注入 Bean 实例对象中并设置好的,回到AbstractAutowireCapableBeanFactory 的 populateBean()方法,对属性依赖注入的代码如下:

2021052723425260341.png2021052723425310443.png2021052723425335945.png2021052723425367347.png2021052723425425649.png

分析上述代码,我们可以看出,对属性的注入过程分以下两种情况:

1)、属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。

2)、属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。

对属性值的解析是在 BeanDefinitionValueResolver 类中的 resolveValueIfNecessary()方法中进行的,对属性值的依赖注入是通过 bw.setPropertyValues()方法实现的,在分析属性值的依赖注入之前,我们先分析一下对属性值的解析过程。

解析属性注入规则

当容器在对属性进行依赖注入时,如果发现属性值需要进行类型转换,如属性值是容器中另一个 Bean实例对象的引用,则容器首先需要根据属性值解析出所引用的对象,然后才能将该引用对象注入到目标实例对象的属性上去,对属性进行解析的由 resolveValueIfNecessary()方法实现,其源码如下:

2021052723425467251.png2021052723425503553.png2021052723425546155.png2021052723425593757.png2021052723425623859.png

通过上面的代码分析,我们明白了 Spring 是如何将引用类型,内部类以及集合类型等属性进行解析的,属性值解析完成后就可以进行依赖注入了,依赖注入的过程就是 Bean 对象实例设置到它所依赖的 Bean对象属性上去。

而真正的依赖注入是通过 bw.setPropertyValues()方法实现的,该方法也使用了委托模式 , 在 BeanWrapper 接 口 中 至 少 定 义 了 方 法 声 明 , 依 赖 注 入 的 具 体 实 现 交 由 其 实 现 类BeanWrapperImpl 来完成,下面我们就分析依 BeanWrapperImpl 中赖注入相关的源码。

注入赋值

BeanWrapperImpl 类主要是对容器中完成初始化的 Bean 实例对象进行属性的依赖注入,即把 Bean对象设置到它所依赖的另一个 Bean 的属性中去。

然而,BeanWrapperImpl 中的注入方法实际上由AbstractNestablePropertyAccessor 来实现的,其相关源码如下:

2021052723425645961.png2021052723425693163.png2021052723425748865.png2021052723425785667.png

通过对上面注入依赖代码的分析,我们已经明白了 Spring IOC 容器是如何将属性的值注入到 Bean 实例对象中去的:

1)、对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。

2)、对于非集合类型的属性,大量使用了 JDK 的反射机制,通过属性的 getter()方法获取指定属性注入以前的值,同时调用属性的 setter()方法为属性设置注入后的值。

看到这里相信很多人都明白了Spring的 setter()注入原理。

如果你能认真的看完,那么看到这个时序图,你一定很熟悉,跟着图在来理解一遍,更加深刻。(高清大图,评论【DI】即可)

2021052723425833269.png

至此 Spring IOC 容器对 Bean 定义资源文件的定位,载入、解析和依赖注入已经全部分析完毕。

现在Spring IOC 容器中管理了一系列靠依赖关系联系起来的 Bean,程序不需要应用自己手动创建所需的对象,Spring IOC 容器会在我们使用的时候自动为我们创建,并且为我们注入好相关的依赖,这就是Spring 核心功能的控制反转和依赖注入的相关功能。

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 《Spring源码解析(七)》从源码深处体验Spring核心技术--Spring自动装配之依赖注入
上一篇
《Spring源码解析(六)》从源码深处体验Spring核心技术--面试中IOC那些鲜为人知的细节
下一篇
《Spring源码解析(八)》从源码深处体验Spring核心技术--AOP那些你不得不明白的概念