Spring DI三部曲之实例化

核心提示什么是依赖注入?依赖注入是我们可以用来实现 IoC 的一种模式,其中被反转的控制是设置对象的依赖关系。将对象与其他对象连接起来,或将对象“注入”到其他对象中,是由程序完成的,而不是由对象本身完成的。Spring中的DI当大家开始学习spri

什么是依赖注入?

依赖注入是我们可以用来实现 IoC 的一种模式,其中被反转的控制是设置对象的依赖关系。

将对象与其他对象连接起来,或将对象“注入”到其他对象中,是由程序完成的,而不是由对象本身完成的。

Spring中的DI

当大家开始学习spring的时候,都会从如下的示例代码开始,在前面几篇文章中详细讲解了第一行代码,spring是如何实现IoC的,现在我们要讲解spring是如何实现DI的。

注意:本文是以5.2.3版本为讲解。

步骤一:获取bean 实例

返回指定 bean 的一个实例,该实例可以是共享的,也可以是独立的。

此方法允许将 Spring BeanFactory 用作 Singleton 或 Prototype 设计模式的替代品。在 Singleton bean 的情况下,调用者可以保留对返回对象的引用。

该方法的具体实现是在“AbstractBeanFactory”类中实现的。

步骤二:获取IoC容器中指定名称的Bean实例

在这方法里调用“doGetBean”方法,这个方法才是真正向IoC容器获取Bean对象

步骤三:获取bean实例

这个方法代码比较多,大致业务逻辑如下:

transformedBeanName:获取bean 名称,必要时去除工厂取消引用前缀“&”,并将别名解析为规范名称

geetSingleton:获取指定名称的单例对象。检查已经实例化的单例,还允许对当前创建的单例进行早期引用

getObjectForBeanInstance:如果单例对象非空,获取bean 实例的对象,bean 实例本身或其创建的对象

检查当前bean工厂是否包含该bean,如果没有找到检查父级容器中是否包含该bean,如果父级容器还找不到,使用显式参数委托给父级容器查找

markBeanAsCreated:将指定的 bean 标记为已创建。这允许 bean 工厂优化其缓存以重复创建指定的 bean。

getMergedLocalBeanDefinition:获取一个RootBeanDefinition,如果指定的 bean 对应于子 bean 定义,则遍历父 bean 定义

checkMergedBeanDefinition:判断RootBeanDefinition是抽象类,则抛异常

getDependsOn:获取当前Bean所依赖Bean的名称,递归调用getBean方法进行注册

如果是单例模式,通过“getSingleton”方法获取名称注册的单例对象,如果尚未注册,则通过“createBean”方法创建并注册一个新对象

如果是原型模式,通过“beforePrototypeCreation”方法实现将原型注册为当前正在创建中,然后通过“createBean”方法创建并注册一个新对象,通过“afterPrototypeCreation”方法执行创建原型后的回调,将原型标记为不再处于创建状态,最后通过“getObjectForBeanInstance”方法获取给定 bean 实例的对象。

如果既不是单例模式也不是原型模式,则根据Bean配置的生命周期,实例化Bean对象,如:request、session、application等生命周期

检查所需类型是否与实际 bean 实例的类型匹配,由于requiredType为空,这里不做考虑

在这里我们重点看下“createBean”方法,在这里会创建一个bean实例。

步骤四:创建一个 bean 实例

大致业务逻辑如下:

为给定的合并 bean 定义创建一个 bean 实例。如果是子定义,bean 定义将已经与父定义合并。

所有 bean 检索方法都委托给此方法以进行实际的 bean 创建。

此类的中心方法:创建 bean 实例、填充 bean 实例、应用后处理器等。

在这个方法中我们看到一个以“doXXX”开头的方法,他就是真正创建实例的方法。这个方法大致业务逻辑如下:

resolveBeanClass:判断需要创建的bean是否可以实例化,即是否可以通过当前的类加载器加载

prepareMethodOverrides:验证并准备 bean 定义的方法覆盖

resolveBeforeIInstantiation:如果该bean配置了实例化前后处理器,则BeanPostProcessors返回一个代理对象而不是bean实例,注意在这里spring实现了AOP

doCreateBean:实际创建指定的bean实例

步骤五:实际创建指定的bean

此时已经进行了预创建处理,例如检查 postProcessBeforeInstantiation 回调。区分默认 bean 实例化、使用工厂方法和自动装配构造函数。

大致业务逻辑如下:

createBeanInstance:把Bean对象封装成BeanWrapper对象

applyMergedBeanDefiinitionPostProcessors:应用后置处理器

addSingletonFactory:向容器中缓存单例对象来防止循环引用

populateBean:使用 bean 定义中的属性值填充 BeanWrapper 中的 bean 实例

initializeBean:初始化实例

获取已经注册单例模式的bean对象,如果根据名称获取的已注册的bean和正在实例化的bean是同一个,那么当前实例化的bean初始化完成;否则,当前bean依赖其他bean,并且当发生循环引用时不允许新创建实例对象,获取当前bean所依赖的其他bean,对依赖bean进行类型检查

将 bean 添加到容器中

步骤六:把Bean对象封装成BeanWrapper对象

使用适当的实例化策略为指定的 bean 创建一个新实例:工厂方法、构造函数自动装配或简单实例化。

大致业务逻辑如下:

resolveBeanClass:确保此时实际解析了 bean 类

obtainFromSupplier:从给定的supplier获取一个 bean 实例

instantiateUsingFactoryMethod:使用工厂方法获取一个 bean 实例

autowireConstructor:按照参数类型匹配bean的构造方法获取一个bean实例

determiineConstructorsFromBeanPostProcessors:确定首选的构造函数

autowireConstructor:使用首选的构造函数获取一个bean实例

instantiateBean:使用无参构造函数获取一个bean实例

步骤七:使用默认构造函数实例化 bean

大致业务逻辑如下:

获取系统的安全管理接口非空,使用匿名内部类,根据实例化策略创建实例对象;否则使用默认策略来创建实例,默认策略类是SimpleInstantiationStrategy类

initBeanWrapper:使用在该工厂注册的自定义编辑器初始化给定的 BeanWrapper

步骤八:使用初始化策略实例化bean对象

这个方法业务很简单:如果bean有方法被覆盖了,则使用JDK的反射机制进行实例化,否则,使用CGLib进行实例化。

步骤九:使用JDK的反射机制实例化bean对象

步骤十:使用CGLib实例化bean对象

大致业务逻辑如下:

createEnhancedSubclass:使用 CGLIB 为提供的 bean 定义创建 bean 类的增强子类

Bean.instantiateClass:如果构造器为空,使用JDK的反射机制实例化bean对象

enhancedSubclassConstructor:使用CGLib实例化bean对象

步骤十一:使用 bean 定义中的属性值填充 BeanWrapper 中的 bean 实例

这个方法虽然代码量很多,其实业务很简单:

获取容器在解析Bean定义资源时为BeanDefinition中设置的属性值PropertyValues

applyPropertyValues:对属性进行注入

步骤十二:解析并注入依赖属性

应用给定的属性值,解析对此 bean 工厂中其他 bean 的任何运行时引用。必须使用深拷贝,所以我们不会永久修改这个属性。

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

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

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

对属性值的解析是在 BeanDefinitionValueResolver 类中的 resolvevalueIfNecessary方法中进行的, 对属性值的依赖注入是通过 bw.setPropertyValues方法实现的。

步骤十三:解析属性注入规则

在这个方法中我们看到对多种情况进行解析,比如:对引用类型的属性进行解析、对属性值是引用容器中另一个Bean名称的解析、对Bean类型属性的解析,主要是Bean中的内部类、对集合数组类型的属性解析等等。

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

下篇文章将将讲解真正的依赖注入,该方法使用了委托模式。

时序图

写在最后

好兄弟可以点赞并关注我的公众号“javaAnswer”,全部都是干货。

 
友情链接
鄂ICP备19019357号-22