2. 使用容器
在本文的第一部分中,我们对容器的初始化操作refresh()方法做了全面的解读,了解了容器构造的整个过程,包括读取bean对象的定义,配置BeanFactoyPostProcessor&BeanPostProcessor接口,以及其它的属性. 在第一部分的解读过程中,还遗留了一个方法,就是getBean()方法.
getBean()方法的使用场景有以下几个,从字面上看,这个方法就是从容器中获取相应的实例对象,那它具体的实现过程又是怎么样的呢?
- 容器初始化结束后,会调用preInstantiateSingleton()方法来实例化lazyInit属性为false的”单例”对象
- 应用程序调用getBean()方法获取对象
|
|
查看ClassPathXmlApplicationContext类的getBean()方法,可以看到它调用的是AbstractApplicationContext类的相应实现,而AbstractApplicationContext方法中是将这个方法的实现委托给内部的BeanFactory对象来实现,从本文第一部分的解读中我们知道,ClassPathXmlApplicationContext内部的BeanFactory对象是在refresh()方法中调用obtainFreshBeanFactory()方法时生成的,默认是DefaultListableBeanFactory的实例,因此我们直接查看DefaultListableBeanFactory类的相应实现即可.
|
|
从DefaultListableBeanFactory类的实现源码可以看到,获取bean实例对象可以分成以下几个步骤:
- 获取容器中指定类型的bean名称
- 如果获取的bean名称超过1个,则对名称进行过滤
- 判断过滤后的bean名称个数,并根据相应的个数获取实例对象
接下来我们来详细地看下这些步骤的执行过程.
2.1 获取指定类型的bean名称
这个步骤是通过getBeanNamesForType()方法来完成的,它是获取容器中符合指定类型的bean对象的名称. 首先来看下这个方法的实现, 可以看到,方法首先会判断当前容器是否处于“冻结配置”的阶段(在容器完成refresh()操作后,配置就会处于“冻结”状态), 如果处于冻结状态,容器就会尝试从缓存中获取相应的名称. 当从缓存中无法获取相应的名称时,才会调用doGetBeanNamesForType()方法进行解析,解析完后会将结果放入缓存.
|
|
从上面的解读中可以知道,实际的工作是在doGetBeanNamesForType()方法中完成的. 这个方法的实现可以分成两个部分,一部分是遍历容器中定义的所有对象的类型,找到其中匹配的对象;另一部分是遍历自定义的单例对象,找到其中匹配的对象名称. 但是不管是遍历哪部分的对象,其实现的思路都是一致的
- 首先匹配beanName对象的类型与请求的类型是否一致
- 不一致的情况下,再尝试匹配beanName的工厂对象创建的对象类型与请求的类型是否一致
具体的匹配操作是通过isTypeMatch()方法来完成的.
|
|
isTypeMatch()方法的作用是判断提供的beanName对象是否与请求的类型匹配,在判断的过程,它会考虑beanName是否为FactoryBean,以及需要判断的类型是FactoryBean本身还是它创建的对象. 它的实现可以分成三个步骤
- 从缓存中获取实例对象
这里可以看到,当缓存中获取到对象时,它首先会判断该对象类型是否为FactoryBean,如果是的话它还需要判断是否比较的是FactoryBean本身的类型还是它创建的对象类型,这点是通过beanName来判断的. 如果不是FactoryBean, 则直接判断返回的对象类型与请求的类型是否匹配即可.
|
|
- 缓存获取失败的情况下,如果当前容器不包含bean的定义,且父容器不为空,则交给父容器判断类型匹配的工作
- 否则尝试从容器中定义的bean信息判断类型匹配信息
这里首先判断bean定义的对象是否是代理对象(dbd!=null),如果是则判断被代理对象的类型能否满足条件,否则直接获取当前bean定义的类型. 在得到类型后,仍然需要判断当前对象是否是FactoryBean以及是否需要匹配的是FactoryBean对象本身的类型,这点和从缓存中获取到对象后的逻辑一样.
|
|
在上面的解读中可以看到,获取beanName对象的实际类型是通过predictBeanType()方法来实现的,这个方法最终会调用doResolveBeanClass()方法来匹配,这个方法首先会根据typesToMatch参数判断当前是否在进行类型匹配操作,并以此为依据选择类加载器classLoaderToUse, 对于类型匹配操作来讲,Spring容器会使用临时的类加载器,以避免在类型匹配的过程中加载相关的类. 在确定了要使用的类加载器后,就会使用该加载器来加载bean,并返回相应的类信息.
|
|
2.2 过滤bean名称
在getBean()方法的第一个步骤中,首先根据对象的类型获取到了相应的bean名称数组,如果数组的长度超过1,那么需要对这些对象名称进行过滤. 过滤的主要思路就是去掉那些autowireCandidate属性设置为false的bean定义.
这里有个比较容易疑惑的地方,就是在过滤的过程中,也保留了containBeanDefinition()返回false的bean名称. 查看这个方法的javaDoc可以知道,这个方法仅判断当前的BeanFactory容器中是否含有这个名称的对象定义,不会考虑它的父容器中是否有这个bean. 但过滤完后,这些bean名称都是通过getBean()方法来进一步获取具体的对象定义,而getBean()方法在当前beanFactory如果找不到这个定义,会尝试从它的父容器中寻找,因此这里在过滤的时候,需要保留那些在当前容器中并不存在的bean名称.
|
|
2.3 根据名称获取bean对象实例
在完成了第二步的过滤后,接着就会用名称从容器中查找相应的bean实例. 这里分几种情况考虑:
- 如果只有一个名称满足条件,那么直接尝试获取这个名称指定的实例就可以了
- 如果满足条件的有多个名称 ,那么需要依次按primary, prirority进行过滤,直到找到唯一的实例对象为止
- 如果没有任何名称满足条件,则尝试使用父容器获取相应的实例对象
- 否则直接抛出异常
2.3.1 多个名称符合条件的情况
对于第一种情况,只有一个名称满足条件,这里暂时略过,后面统一对getBean()这个方法进行详述. 而第三种情况,其实是在父容器中调用同样的方法,也直接跳过. 这里主要解读下第二种情况,从源码中可以看出,在多个名称符合条件的情况下,Spring框架首先会依次获取这些名称的对象,然后通过determinePrimaryCandidate()方法判断其中是否有唯一的primary属性为true的实例对象,如果有,直接返回这个对象; 如果没有找到,那么会继续调用determiHighestPriorityCandidate()方法判断是否有唯一的Priority注解的对象,如果有,直接返回这个对象,如果还是没有找到那么说明存在多个对象符合条件,那么就抛出异常.
|
|
首先来看看determinePrimaryCandidate()方法的实现,这里依次判断每个候选对象的primary属性,如果有超过一个对象设置primary属性为true, 那么直接抛出异常;如果只有一个对象设置了这个属性,则直接返回这个对象,根据上面的解读,如果只有唯一一个对象设置了这个属性值,那么这个对象将会被直接返回,否则返回null.
|
|
另外再来看看determineHighestPriorirtyCandidate()方法. 应该来讲,这里的实现思路和上面determineByPrimaryCandidate()方法的思路完全一样,只不过判断是priority,在此基础上还增加了priority属性的比较,其它的都一样. 这里就不再赘述.
|
|
2.3.2 只有一个名称符合条件的情况
到这里,我们就把所有特殊的情况都解读过了,现在只剩下最基本的情况,就是根据bean名称来获取相应的实现,这也是在getBean()方法中实现的. 在AbstractBeanFactory类中可以看到,这个方法最终调用了doGetBean()方法,它的实现过程大体可以分成三个步骤:
- 尝试从已经创建或正在创建的singleton对象中获取,如果有则直接调用getObjectForBeanInstance()返回
- 如果当前的容器中不包含有这个bean的定义且父容器不为空的情况下,尝试从父容器中获取该bean实例对象
- 尝试在当前容器中创建这个bean实例对象,这步又可以进一步细分成以下两个步骤
- 保证当前bean对象的依赖对象已经创建完成
- 根据当前bean对象的作用域创建相应的实例对象
- 创建完对象后,如果类型不符合,那么尝试使用类型转换服务转换成目标类型.
接下来分别对这些步骤进行解读.
2.3.2.1 尝试从缓存中获取singleton
在这一步中,BeanFactory会尝试从singletonObjects属性中读取这个beanName指定的实例对象,如果找到了说明这个对象之前已经被创建过了,直接返回这个对象即可;如果没有找到这个对象,但发现它正在被创建,那么会尝试从earlySingletonObjects属性中查找,如果还是没有找到,则尝试从singletonFactories中查找相应的对象工厂,如果有这个对象的对象工厂,则尝试创建这个对象.
|
|
在获取到sharedInstance对象后,代码中会调用getObjectForBeanInstance()方法,这个方法的作用是对获取的对象进行解析,如果它是普通对象或者请求的是工厂对象本身(bean名称以’&’开头)则直接返回; 否则就使用工厂方法创建相应的对象. 这里的逻辑相对简单,就不做过多的阐述.
|
|
2.3.2.2 从父容器中获取相应的bean实例对象
如果当前容器对象不包含有这个beanName, 且父容器不为空的情况下,会尝试从父容器中获取这个bean实例
|
|
2.3.2.3 尝试从当前容器中加载bean对象
当父容器为空,或者当前容器中含有指定的bean名称的定义,那么就会在当前容器中尝试加载该对象. 加载的过程可以分成两个步骤,第一个是确保当前对象依赖的所有对象都已经创建完成,第二个是根据对象作用域的不同创建相应的对象.
- 确保依赖的对象已经创建完成
|
|
对于每个依赖的对象,都调用isDependent方法来确认是否存在循环依赖关系. isDependent的作用是判断第二个参数指定的对象是否依赖于第一个参数的对象,如果后者依赖于前者,而前者的dependsOn属性中又包含有第二个,说明两者之前有循环依赖关系,这时直接抛出循环依赖异常.
|
|
如果不存在循环依赖关系,则调用registerDependentBean方法注册对象间的依赖关系. 这个方法会往dependentBeanMap中插入相应的名称,这个属性维护了当前对象以及依赖于这个对象的对象名称集合,另外这个方法还会往dependenciesForBeanMap属性中插入数据,这个属性维护的是当前对象以及当前对象所依赖的对象名称集合.
|
|
- 根据对象的作用域创建相应的对象
在确保依赖的对象都已经创建完成后,就可以开始创建当前对象了. 这里要根据对象的作用域(scope)来采用不同的方式创建. 先来看看singleton对象的创建. 这里调用了getSingleton方法来获取对象,并提供了匿名的ObjectFactory实现. 在getSingleton方法中,其实就是回调了ObjectFactory类的getObject方法, 这是“策略模式”的实践. 在获取到sharedInstance对象后,会调用getObjectForBeanInstance方法,这个方法的作用在前面已经提及过,它是用来判断sharedInstance对象的类型,如果是FactoryBean,则调用相应的方法返回对象,如果它是普通对象或请求的就是工厂对象本身,则直接返回.
关于createBean()方法的执行过程,下一篇会有详细的解读.
|
|
再来看看prototype对象的创建. 由于prototype域对象的特性是每次请求对象时都返回新的对象,因此这里的创建过程相对简单,就是调用createBean()方法返回新的实例,再调用getObjectForBeanInstance()方法进行转换即可.
|
|
除此之外,Spring还支持自定义Scope,对于自定义Scope的对象来讲,它的创建过程与之前的两种情况大同小异,这里就不做过多阐述.
|
|
2.3.2.4 类型转换
在经过上述步骤后,bean对象的实例就基本上创建好了,这时候还需要再进一步判断当前获取的实例对象类型与实际请求的类型是否匹配,如果不匹配的话,还需要进行类型转换操作. 类型转换主要是通过TypeConverter接口来完成的.
|
|