2.4 createBean()方法的实现
在上面的解读中,我们知道getBean()方法的实现过程,首先尝试从缓存中获取,缓存获取失败后则尝试创建对象,在创建对象时首先检查依赖关系,在满足了依赖关系后就会调用createBean()对象获取实例. 那createBean()方法又是如何实现的呢?createBean()底层调用了doCreateBean()方法, 后者的实现大体上可以分成几个步骤:
- 调用createBeanInstance创建对象实例
- 调用populateBean()方法填充对象
- 调用initilizingBean()方法初始化对象
接下来就来看看这三个步骤的实现细节.
2.4.1 createBeanInstance()方法
调用createBeanInstance()方法创建实例的实现可分为两种,一种是通过工厂对象来创建,一种是直接调用对象的构造方法来创建.
2.4.1.1 使用工厂对象创建实例
首先先来看看使用工厂对象来实例化对象的代码, 这个部分主要是调用了instantiateUsingFactoryMethod()方法来实现的,这个方法的实现代码非常复杂,总得来讲,可分为几个步骤:
- 确定工厂对象的工厂方法factoryMethod及其参数
- 根据第1步中确定的工厂方法和参数实例化对象
容器首先会尝试从bean定义的消息中获取定义的工厂方法factoryMethodToUse和方法参数argToUse,这里首先会查看mbd中是否已经能解析过的构造函数和方法,如果有则继续搜索是否有解析过的构造参数,如果没有则调用resolvePreparedArguments()方法进行解析.
|
|
resolvePreparedArguments()方法的作用就是mbd中定义的工厂方法的参数信息,它通过遍历每个参数并判断参数的类型完成解析操作,其中最重要的是当参数是AutowiredArgumentMarker类型时,它会调用resolveAutowireArgument()方法完成参数的注入操作,关于这个方法会在后续的解读中进行详述,这里只需要知道它是用来解析参数并完成自动注入的即可.
|
|
在经过上述的解析操作后,只要factoryMethodToUse和areToUse参数中有一个是缺失的,那么容器就需要遍历所有可能的方法来寻找相应的工厂方法和参数,寻找候选方法的过程是获取工厂对象中声明的所有方法,然后找到其中名称与工厂方法factoryMethod属性一致的候选方法,
|
|
在得到候选方法之后,就需要对这些候选方法进行排序,排序的规则是public方法先于其它类型方法,参数个数多的优先.
|
|
排序完后,就需要得到这些方法中匹配度最高的那个方法,匹配度的规则为类型匹配,具体规则可以参阅这里. 如果存在两个匹配度完全一样的候选方法,那么会抛出异常.
|
|
在遍历完所有的候选方法后,正常情况下就应该能获取到唯一的工厂方法factoryMethodToUse,但也不排除可能会有多个方法同时满足条件,因此这里需要再对遍历后的结果进行判断.
|
|
如果以上的检查没有抛出任何异常,那么说明容器已经找到当前对象的工厂方法及相应的参数,可以开始创建实例的操作了. 这里需要注意的是beanFactory容器并不是自行实例化对象,而是通过InstantiationStrategy接口来完成的,默认的实现是CglibSubclassInstantiateStrategy, 也就是通过Cglib继承子类的方式来创建对象,这也是Spring框架中非常值得学习的一点,面向接口编程,提供最大限度地灵活性. 到此为止,容器通过工厂对象创建实例的操作就算全部完成了.
|
|
2.4.1.2 直接实例化对象
创建实例对象的另一种方式就是直接通过bean对象的构造函数. 可以看到,Spring框架在尝试通过构造函数实例化对象时会根据传入的参数是否为空,是否有解析过的构造函数,是否有已经解析过的构造函数参数来决定具体调用的实例化方法.
- 当传入的构造方法参数不为空时,容器会先调用detemineConstructorsFromBeanPostProcessors方法来获取可用的构造函数, 并最终调用autowireConstructor()方法来实例化对象
- 当传入的参数为空时,会判断是否已经有解析过的构造方法,如果有,则继续判断是否有解析过的方法参数,并根据情况选择autowireConstructor()方法还是instantiateBean()方法
- 如果所有的判断条件都不满足,则直接调用instantiateBean()方法来实例化对象
|
|
通过以上的分析可以知道,Spring框架是通过instantiateBean()方法和autowireConstructor()方法来进行实例化操作. 现在就来具体看看这两个方法的作用与实现.
autowireConstrutor()方法
根据autowireConstrutor文档的说明,这个方法是用来解决”构造函数自动注入”的地方, 它能够自动完成构造函数参数的自动注入. 查看这个方法的源码会发现,它的实现与上一小节“使用工厂对象创建实例”基本一样,都是先确定使用的构造方法及参数,选择候选的构造方法、排序、匹配等规则也完全一样,这里就不再做详述,有兴趣的读者可以参阅这部分源码或参考上一小节的解读.
instantiateBean()方法
这个方法的实现相对简单,通过InstantiateStrategy接口完成实例创建即可.
|
|
到此为止,不管是通过工厂对象创建,还是直接通过构造函数创建,Spring容器已经完成了createBean()方法中的第一步,创建BeanWrapper的操作. 有了实例对象后,就需要对实例的属性进行填充及初始化操作. 这部分内容将会在下一篇文章中进行详细阐述.
2.4.2 populateBean()方法
在创建完bean对象之后,就需要填充对象的属性,这个操作由populateBean()方法完成. populateBean()方法可以分成四个步骤:
- 调用InstantiationAwareBeanPostProcessor接口的afterInstantiation()方法,这个接口定义了三个方法beforeInstantiazion, afterInstantiation以及postProcessorPropertyValues(), 分别用于在实例化对象前后以及在设置对象的属性值之前进行处理. beforeInstantiation()方法在调用createBean()方法之前就已经被调用过了,这里是在实例化对象之后,所以调用的是afterInstantiation()方法,而postProcessorPropertyValues()方法会在解析完属性值之后,填充到对象之前被调用,在populateBean()方法的第三步会碰到.
- 根据bean对象的定义,选择合适的自动注入模式(byName或byType), 并解析相应的参数
- 调用InstantiationAwareBeanPostProcessor接口的postProcessorPropertyValues()方法,并检查当前对象的所有属性值是否已经解析完成
- 填充属性值
2.4.2.1 调用postProcessAfterInstantiation()方法
可以看到,这里的实现就是获取容器中定义的所有InstantiationAwareBeanPostProcessor接口,并依次调用它们的postProcessAfterInstantiation()方法即可,没有太多复杂的逻辑,不作赘述.
|
|
2.4.2.2 根据自动注入模式获取属性值
bean对象的自动注入模式是在定义bean时通过autowire属性来设置的,可选的值包括byName, byType以及constructor, 其中constructor的情况已经在创建对象实例时处理过了,因此这里只需要考虑byName和byType两种情况. 顾名思义,byName就是通过属性的名称与容器中定义的bean名称进行自动注入,而byType则是根据类型注入.
|
|
根据bean名称自动注入
从autowireByName()的实现可以看出,这个模式的自动注入过程比较简单,首先获取当前bean对象中还没有被设置过的非简单类型的属性(Spring框架不允许简单类型的自动注入), 然后遍历这些属性,根据属性的名称尝试从容器中获取相应的bean对象,得到依赖的对象后,记录该属性对应的值,并设置相应的依赖关系表即可.
|
|
根据类型自动注入
autowireByType()方法的实现思路和byName()的思路是一样的,也是先获取当前bean对象中没有被设置过的非简单类型的属性,然后遍历这些属性,只不过这里不是根据bean对象的名称完成注入,而是调用了resolveDependency()方法来获取与属性匹配的对象,并将该对象注入到属性中,设置相应的依赖关系. resolveDependency()方法的实现稍微有些复杂,这里暂时略过,本文最后会对这个方法进行详述.
|
|
2.4.2.3 调用postProcessPropertyValues()并检查依赖
根据配置的注入模式获取到相应的值之后,就可以调用InstantiationAwareBeanPostProcessor接口的postProcessPropertyValue了,另外,考虑到在解析属性值的过程中,有些属性值可能会解析不到相应的对象,因此在赋值之前还需要校验是否所有的属性值都已经解析完成了.
|
|
2.4.2.4 设置属性值
在经过前面三步之后,Spring容器已经获取到所有属性的值,并且已经完成了相应的BeanPostProcessor接口的调用及依赖关系的校验,最后一步就是将这些属性值赋给对象的相应属性即可.
2.4.3 initilizeBean()方法
在调用populateBean()方法填充完bean对象后,对象的创建工作就基本完成了. Spring容器中定义了定义了生命周期方法,用于在构造完对象之后和析构对象之前执行定制化的操作,其中包括InitializingBean接口,Aware接口以及配置文件中的init-method属性. 这些属性的处理都是在initializeBean()方法中进行处理的.
可以看到, initializeBean()方法主要是四个步骤:
- 调用Aware相关的接口,其中包括BeanNameAware接口,BeanClassLoaderAware接口以及BeanFactoryAware接口
- 调用BeanPostProcessor的postProcessorBeforeInitialization()方法
- 调用initMethod方法,包括InitializingBean接口以及init-method属性定义的方法, 其中InitializingBean接口先于init-method方法执行
- 调用BeanPostProcessor的postProcessorAfterInitialization()方法
|
|
2. 5 resolveDependency()方法
在前面提到的populateBean()方法中,如果bean对象注入的模式是byType, 那么Spring容器会调用resolveDependency()方法来解析属性的注入值,现在就来详细地看下这个方法的实现. resolveDependency()方法的实现主要是由doResolveDependency()方法来完成,这个方法的实现可以分成三个步骤:
- 首先尝试从autowireCandidateResolver接口尝试获取请求的类型是否有建议值,如果有则直接返回这个值即可,当然在返回前,可能还需要使用容器的类型转换.
|
|
判断当前的属性类型是否为Array, Collection以及Map类型
这种情况下,框架首先会分别获取Array数组元素类型,Collection元素类型以及Map中的Value类型, 然后再以这些元素类型为请求类型,调用findAutowireCandidates()方法查找候选的注入对象,返回所有的匹配对象.
尝试从容器中找到所有与请求类型匹配的对象
这步主要是通过findAutowireCandidates()方法来完成的. findAutowireCandidates()方法首先从resolvableDependencies表中查找请求类型的值,然后遍历所有与请求类型匹配的对象,依次检查它们是否有可能是候选的对象,这步是通过isAutowireCandidate()方法来完成的,这个方法会在后续进行说明. 遍历结束后如果没有候选的对象,那么Spring容器还会尝试备选的描述符再次进行匹配,如果匹配结果还是为空,Spring框架会把”自我引用”的情况也考虑在内,最终返回可能的候选对象.
|
|
在找到备选对象后,如果存在多个备选对象的情况,Spring容器还会调用determineAutowireCandidate()方法来决定最终注入哪个对象. 选择的依据就是bean的配置信息中是否设置了primary属性,或者设置了Priority注解. 对于primary属性,必须有且只有一个对象配置了这个属性,否则会抛出异常;而对于Priority注解,则不能存在两个优先级相同的最高优先级对象,否则也会抛出异常. 如果所有的候选对象中都没有设置这些属性值,Spring框架会尝试根据候选对象的别名进行匹配,直到找到匹配对象为止.
|
|
2.6 isAutowireCandidate()方法
在上面解读resolveDependency()方法的过程中,我们路过了isAutowireCandidate()方法的说明,这里就来看看它的实现. 可以看到,这个方法的最后是调用了AutowireCandidateResolver接口来完成判断,这里可以认为是“策略模式”的实现. 以当前的例子来讲,DefaultListableBeanFactory类中默认使用的是SimpleAutowireCandidateResolver实现,这个实现在判断某个对象能否成为候选的注入对象时,以bean定义中的autowire-candidate属性为准.
|
|
总结
到此为止,我们已经把Spring框架中提供的IoC模块的实现源码做了比较简单的解读,这里做个总结,Spring框架提供的IoC机制可以通过读取配置文件及注解的方式,自动管理应用程序的对象以及它们之间的依赖关系,整个实现过程可以分成两个大的步骤:
- 调用refresh()方法加载bean定义,并配置相应的BeanFactoryPostProcessor, BeanPostProcessor, MessageSource以及事件监听器等设置. 加载bean定义主要是通过读取配置文件的方式进行,对于默认的beans命名空间有默认的读取方式,而对于自定义的命名空间,则是通过NamespaceHandlerResolver来获取相应的NamespaceHandler对象,并交由它来读取相应的配置
- 当读取完对象的定义后,应用程序就可以调用getBean()方法来获取容器中管理的对象,这个方法的实现可大体分成三个步骤:
- 根据类型解析名称
- 如果存在多个满足条件的名称,则按一定的规则进行过滤,获取匹配度最高的那个bean名称
- 根据bean名称获取对象实例,获取的过程可简单概括为: 先确保它依赖的所有对象都已经完成创建,然后调用createBean()方法创建对象,在createBean()方法的实现中包括createBeanInstance(),populateBean()以及InitializeBean()三个步骤.
通过解读Spring IoC模块的源码,我们对Spring框架有了基本的了解,后续还需要继续深入理解AOP, MVC模块,才能够形成更加全面的认识.
参考文献
- 官方文档
- Spring JavaDoc]