本文共 26011 字,大约阅读时间需要 86 分钟。
为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的文章。在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一些建议。在做完必要的准备工作后,从本文开始,正式开始进入源码分析的阶段。
在本篇文章中,我将会详细分析BeanFactory
的getBean(String)
方法实现细节,getBean(String)
及所调用的方法总体来说实现上较为复杂,代码长度比较长。作为源码分析文章,本文的文章长度也会比较长,希望大家耐心读下去。
好了,其他的不多说了,进入主题环节吧。
简单说一下本章的内容安排吧,在本章的开始,也就是2.1节,我将会分析getBean(String)
方法整体的实现逻辑。但不会分析它所调用的方法,这些方法将会在后续几节中依次进行分析。那接下来,我们就先来看看 getBean(String) 方法是如何实现的吧。
在本小节,我们先从战略上俯瞰 getBean(String) 方法的实现源码。代码如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 | public Object getBean(String name) throws BeansException { // getBean 是一个空壳方法,所有的逻辑都封装在 doGetBean 方法中 return doGetBean(name, null, null, false);}protected |
以上就是getBean(String)
和doGetBean(String, Class, Object[], boolean)
两个方法的分析。代码很长,需要一点耐心阅读。为了凸显方法的主逻辑,大家可以对代码进行一定的删减,删除一些日志和异常代码,也可以删除一些不是很重要的逻辑。另外由于 doGetBean 方法调用了其他的很多方法,在看代码的话,经常会忘掉 doGetBean 所调用的方法是怎么实现的。比如 getSingleton 方法出现了两次,但两个方法并不同,在看第二个的 getSingleton 方法时,可能会忘掉第一个 getSingleton 是怎么实现的。另外,如果你想对比两个重载方法的异同,在 IDEA 里跳来跳去也是很不方便。为此,我使用了 sublime 进行分屏,左屏是删减后的 doGetBean 方法,右屏是 doGetBean 调用的一些方法,这样看起来会方便一点。忘了某个方法的实现逻辑后,可以到右屏查看,也可进行对比。分屏效果如下:
这里我为了演示,删除了不少东西。大家可以按需进行删减,并配上注释,辅助理解。
看完了源码,下面我来简单总结一下 doGetBean 的执行流程。如下:
以上步骤对应的流程图如下:
在获取 bean 实例之前,Spring 第一件要做的事情是对参数 name 进行转换。转换的目的主要是为了解决两个问题,第一个是处理以字符 & 开头的 name,防止 BeanFactory 无法找到与 name 对应的 bean 实例。第二个是处理别名问题,Spring 不会存储 <别名, bean 实例> 这种映射,仅会存储 <beanName, bean>。所以,同样是为了避免 BeanFactory 找不到 name 对应的 bean 的实例,对于别名也要进行转换。接下来,我们来简单分析一下转换的过程,如下:
12345678910111213141516171819202122232425262728293031323334353637383940 | protected String transformedBeanName(String name) { // 这里调用了两个方法:BeanFactoryUtils.transformedBeanName(name) 和 canonicalName return canonicalName(BeanFactoryUtils.transformedBeanName(name));}/** 该方法用于处理 & 字符 */public static String transformedBeanName(String name) { Assert.notNull(name, "'name' must not be null"); String beanName = name; // 循环处理 & 字符。比如 name = "&&&&&helloService",最终会被转成 helloService while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); } return beanName;}/** 该方法用于转换别名 */public String canonicalName(String name) { String canonicalName = name; String resolvedName; /* * 这里使用 while 循环进行处理,原因是:可能会存在多重别名的问题,即别名指向别名。比如下面 * 的配置: * |
对于单例 bean,Spring 容器只会实例化一次。后续再次获取时,只需直接从缓存里获取即可,无需且不能再次实例化(否则单例就没意义了)。从缓存中取 bean 实例的方法是getSingleton(String)
,下面我们就来看看这个方法实现方式吧。如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | public Object getSingleton(String beanName) { return getSingleton(beanName, true);}/** * 这里解释一下 allowEarlyReference 参数,allowEarlyReference 表示是否允许其他 bean 引用 * 正在创建中的 bean,用于处理循环引用的问题。关于循环引用,这里先简单介绍一下。先看下面的配置: * * |
上面的代码虽然不长,但是涉及到了好几个缓存集合。如果不知道这些缓存的用途是什么,上面源码可能就很难弄懂了。这几个缓存集合用的很频繁,在后面的代码中还会出现,所以这里介绍一下。如下:
缓存 | 用途 |
---|---|
singletonObjects | 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 |
earlySingletonObjects | 用于存放还在初始化中的 bean,用于解决循环依赖 |
singletonFactories | 用于存放 bean 工厂。bean 工厂所产生的 bean 是还未完成初始化的 bean。如代码所示,bean 工厂所生成的对象最终会被缓存到 earlySingletonObjects 中 |
关于 getSingleton 先说到这里,getSingleton 源码并不多。但涉及到了循环依赖的相关逻辑,如果对这一块不理解可能不知道代码所云。等后面分析循环依赖的时候,我会再次分析这个方法,所以暂时不理解也没关系。
Spring 支持配置继承,在标签中可以使用parent
属性配置父类 bean。这样子类 bean 可以继承父类 bean 的配置信息,同时也可覆盖父类中的配置。比如下面的配置:
1234567 | |
如上所示,hello-child 配置继承自 hello。hello-child 未配置 class 属性,这里我们让它继承父配置中的 class 属性。然后我们写点代码测试一下,如下:
1234 | String configLocation = "application-parent-bean.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);System.out.println("hello -> " + applicationContext.getBean("hello"));System.out.println("hello-child -> " + applicationContext.getBean("hello-child")); |
测试结果如下:
由测试结果可以看出,hello-child 在未配置 class 的属性下也实例化成功了,表明它成功继承了父配置的 class 属性。
看完代码演示,接下来我们来看看源码吧。如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 | protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { // 检查缓存中是否存在“已合并的 BeanDefinition”,若有直接返回即可 RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName); if (mbd != null) { return mbd; } // 调用重载方法 return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));}protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) throws BeanDefinitionStoreException { // 继续调用重载方法 return getMergedBeanDefinition(beanName, bd, null);}protected RootBeanDefinition getMergedBeanDefinition( String beanName, BeanDefinition bd, BeanDefinition containingBd) throws BeanDefinitionStoreException { synchronized (this.mergedBeanDefinitions) { RootBeanDefinition mbd = null; // 我暂时还没去详细了解 containingBd 的用途,尽管从方法的注释上可以知道 containingBd 的大致用途,但没经过详细分析,就不多说了。见谅 if (containingBd == null) { mbd = this.mergedBeanDefinitions.get(beanName); } if (mbd == null) { // bd.getParentName() == null,表明无父配置,这时直接将当前的 BeanDefinition 升级为 RootBeanDefinition if (bd.getParentName() == null) { if (bd instanceof RootBeanDefinition) { mbd = ((RootBeanDefinition) bd).cloneBeanDefinition(); } else { mbd = new RootBeanDefinition(bd); } } else { BeanDefinition pbd; try { String parentBeanName = transformedBeanName(bd.getParentName()); /* * 判断父类 beanName 与子类 beanName 名称是否相同。若相同,则父类 bean 一定 * 在父容器中。原因也很简单,容器底层是用 Map 缓存 |
上面的源码虽然有点长,但好在逻辑不是很复杂。加上我在源码里进行了比较详细的注解,我想耐心看一下还是可以看懂的,这里就不多说了。
在经过前面这么多的步骤处理后,到这里差不多就接近 doGetBean 方法的尾声了。在本节中,我们来看看从 FactoryBean 实现类中获取 bean 实例的过程。关于 FactoryBean 的用法,我在那篇文章中已经演示过,这里就不再次说明了。那接下来,我们直入主题吧,相关的源码如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 | protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { // 如果 name 以 & 开头,但 beanInstance 却不是 FactoryBean,则认为有问题。 if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass()); } /* * 如果上面的判断通过了,表明 beanInstance 可能是一个普通的 bean,也可能是一个 * FactoryBean。如果是一个普通的 bean,这里直接返回 beanInstance 即可。如果是 * FactoryBean,则要调用工厂方法生成一个 bean 实例。 */ if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { return beanInstance; } Object object = null; if (mbd == null) { /* * 如果 mbd 为空,则从缓存中加载 bean。FactoryBean 生成的单例 bean 会被缓存 * 在 factoryBeanObjectCache 集合中,不用每次都创建 */ object = getCachedObjectForFactoryBean(beanName); } if (object == null) { // 经过前面的判断,到这里可以保证 beanInstance 是 FactoryBean 类型的,所以可以进行类型转换 FactoryBean factory = (FactoryBean ) beanInstance; // 如果 mbd 为空,则判断是否存在名字为 beanName 的 BeanDefinition if (mbd == null && containsBeanDefinition(beanName)) { // 合并 BeanDefinition mbd = getMergedLocalBeanDefinition(beanName); } // synthetic 字面意思是"合成的"。通过全局查找,我发现在 AOP 相关的类中会将该属性设为 true。 // 所以我觉得该字段可能表示某个 bean 是不是被 AOP 增强过,也就是 AOP 基于原始类合成了一个新的代理类。 // 不过目前只是猜测,没有深究。如果有朋友知道这个字段的具体意义,还望不吝赐教 boolean synthetic = (mbd != null && mbd.isSynthetic()); // 调用 getObjectFromFactoryBean 方法继续获取实例 object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object;}protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { /* * FactoryBean 也有单例和非单例之分,针对不同类型的 FactoryBean,这里有两种处理方式: * 1. 单例 FactoryBean 生成的 bean 实例也认为是单例类型。需放入缓存中,供后续重复使用 * 2. 非单例 FactoryBean 生成的 bean 实例则不会被放入缓存中,每次都会创建新的实例 */ if (factory.isSingleton() && containsSingleton(beanName)) { synchronized (getSingletonMutex()) { // 从缓存中取 bean 实例,避免多次创建 bean 实例 Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { // 使用工厂对象中创建实例 object = doGetObjectFromFactoryBean(factory, beanName); Object alreadyThere = this.factoryBeanObjectCache.get(beanName); if (alreadyThere != null) { object = alreadyThere; } else { // shouldPostProcess 等价于上一个方法中的 !synthetic,用于表示是否应用后置处理 if (object != null && shouldPostProcess) { if (isSingletonCurrentlyInCreation(beanName)) { return object; } beforeSingletonCreation(beanName); try { // 应用后置处理 object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's singleton object failed", ex); } finally { afterSingletonCreation(beanName); } } // 这里的 beanName 对应于 FactoryBean 的实现类, FactoryBean 的实现类也会被实例化,并被缓存在 singletonObjects 中 if (containsSingleton(beanName)) { // FactoryBean 所创建的实例会被缓存在 factoryBeanObjectCache 中,供后续调用使用 this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); } } } return (object != NULL_OBJECT ? object : null); } } // 获取非单例实例 else { // 从工厂类中获取实例 Object object = doGetObjectFromFactoryBean(factory, beanName); if (object != null && shouldPostProcess) { try { // 应用后置处理 object = postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex); } } return object; }}private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName) throws BeanCreationException { Object object; try { // if 分支的逻辑是 Java 安全方面的代码,可以忽略,直接看 else 分支的代码 if (System.getSecurityManager() != null) { AccessControlContext acc = getAccessControlContext(); try { object = AccessController.doPrivileged(new PrivilegedExceptionAction |
上面的源码分析完了,代码虽长,但整体逻辑不是很复杂,这里简单总结一下。getObjectForBeanInstance 及它所调用的方法主要做了如下几件事情:
本节涉及到了 FactoryBean 和后置处理两个特性,关于这两个特性,不熟悉的同学可以参考我在一文中的说明,这里就不过多解释了。
到这里,Spring IOC 容器获取 bean 实例这一块的内容就分析完了。如果大家是初次阅读 Spring 的源码,看不懂也没关系。多看几遍,认证思考一下,相信是能看得懂的。另外由于本人水平有限,以上的源码分析有误的地方,还望多指教,谢了。
好了,本文先到这里。又到周五了,祝大家在即将到来的周末玩的开心。over.
更新时间 | 标题 |
---|---|
2018-05-30 | |
2018-06-01 | |
2018-06-04 | |
2018-06-06 | |
2018-06-08 | |
2018-06-11 | |
2018-06-11 |
更新时间 | 标题 |
---|---|
2018-06-17 | |
2018-06-20 | |
2018-06-20 | |
2018-06-22 |
更新时间 | 标题 |
---|---|
2018-06-29 | |
2018-06-30 |
- 本文链接:
from:
转载地址:http://wjwrf.baihongyu.com/