spring源码分析 bean的加载

目录

简介

FactoryBean的使用

缓存中获取单例bean

从bean的实例中获取对象

获取单例

准备创建bean

处理override属性

实例化的前置处理

循环依赖

什么是循环依赖

Spring如何解决循环依赖

创建bean

创建bean的实例

AutowireConstructor

instantiateBean

实例化策略

记录创建bean的ObjectFactory

属性注入

AutowireByName

AutowireByType

applyPropertyValues

初始化bean

激活Aware方法

处理器的应用

激活自定义的init方法

注册DisposableBean


注意:本文摘自spring源码深度解析

简介

经过前面的分析,我们终于结束了对XML配置文件的解析,接下来将会面临更大的挑战,就是对bean加载的探索。bean加载的功能实现远比bean的解析要复杂得多,同样,我们还是以本书开篇的示例为基础,对于加载bean的功能,在Spring中的调用方式为:

MyTestBean bean=(MyTestBean) bf.getBean("myTestBean")

这句代码实现了什么样的功能呢?我们可以先快速体验一下Spring中代码是如何实现的。

仅从代码量上就能看出来bean的加载经历了一个相当复杂的过程,其中涉及各种各样的考虑。相信读者细心阅读上面的代码,并参照部分代码注释,是可以粗略地了解整个Spring加载bean的过程。对于加载过程中所涉及的步骤大致如下。

1 转换对应beanName

或许很多人不理解转换对应beanName是什么意思,传入的参数name不就是beanName吗?其实不是,这里传入的参数可能是别名,也可能是FactoryBean,所以需要进行一系列的解析,这些解析内容包括如下内容。

去除FactoryBean的修饰符,也就是如果name="&aa",那么会首先去除&而使name="aa"。取指定alias所表示的最终beanName, 例如别名A指向名称为B的bean则返回B; 若别名A指向别名B, 别名B又指向名称为C的bean则返回C。

2 尝试从缓存中加载单例

单例在Spring的同一个容器内只会被创建一次,后续再获取bean,就直接从单例缓存中获取了。当然这里也只是尝试加载,首先尝试从缓存中加载,如果加载不成功则再次尝试从singletonFactories中加载。因为在创建单例bean的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,在Spring中创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提早曝光加入到缓存中,一旦下一个bean创建时候需要依赖上一个bean则直接使用ObjectFactory (后面章节会对循环依赖重点讲解)。

3 bean的实例化

如果从缓存中得到了bean的原始状态,则需要对bean进行实例化。这里有必要强词一下,缓存中记录的只是最原始的bean状态,并不一定是我们最终想要的bean。举个例子,假如我们需要对工厂bean进行处理,那么这里得到的其实是工厂bean的初始状态,但是我们真正需要的是工厂bean中定义的factory-method方法中返回的bean,而getObjectForBeanlnstance就是完成这个工作的,后续会详细讲解。

4 原型模式的依赖检查

只有在单例情况下才会尝试解决循环依赖,如果存在A中有B的属性,B中有A的属性,那么当依赖注入的时候,就会产生当A还未创建完的时候因为对于B的创建再次返回创建A,造成循环依赖,也就是情况:isPrototypeCurrentlyInCreation(beanName)判断true。

5 检测parentBeanFactory

从代码上看,如果缓存没有数据的话直接转到父类工厂上去加载了,这是为什么呢?

可能读者忽略了一个很重要的判断条件: parentBeanFactory != null && !containsBeanDefinition (beanName), parentBeanFactory != null。parentBeanFactory如果为空,则其他一切都是浮云,这个没什么说的,但是!containsBeanDefinition(beanName)就比较重要了,它是在检测如果当前加载的XML配置文件中不包含beanName所对应的配置,就只能到parentBeanFactory去尝试下了,然后再去递归的调用getBean方法。

6 将存储XML配置文件的GernericBeanDefinition转换为RootBeanDefinition

因为从XML配置文件中读取到的bean信息是存储在GernericBeanDefinition中的,但是所有的bean后续处理都是针对于RootBeanDefinition的,所以这里需要进行一个转换,转换的同时如果父类bean不为空的话,则会一并合并父类的属性。

7 寻找依赖

囚为bean的初始化过程中很可能会用到某些属性,而某些属性很可能是动态配置的,并且配置成依赖于其他的bean, 那么这个时候就有必要先加载依赖的bean,所以,在Spring的加载顺寻中,在初始化某一个bean的时候首先会初始化这个bean所对应的依赖。

8 针对不同的scope进行bean的创建

我们都知道,在Spring中存在着不同的scope,其中默认的是singleton,但是还有些其他的配置诸如prototype、request之类的。在这个步骤中,Spring会根据不同的配置进行不同的初始化策略。

9 类型转换

程序到这里返回bean后已经基本结束了,通常对该方法的调用参数requiredType是为空的,但是可能会存在这样的清况,返回的bean其实是个String,但是requiredType却传入Integer类型,那么这时候本步骤就会起作用了,它的功能是将返回的bean转换为requiredType所指定的类型。当然,String转换为Integer是最简单的一种转换,在Spring中提供了各种各样的转换器,用户也可以自己扩展转换器来满足需求。

经过上面的步骤后bean的加载就结束了,这个时候就可以返回我们所需要的bean了,

图直观地反映了整个过程。其中最重要的就是步骤8,针对不同的scope进行bean的创建,你会看到各种常用的Spring特性在这里的实现。

在细化分析各个步骤提供的功能前,我们有必要先了解下FactoryBean的用法。

FactoryBean的使用

一般情况下,Spring通过反射机制利用bean的class属性指定实现类来实例化bean。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大批的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.Springframework.bean.Factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。

FactoryBean接口对于Spring框架来说占有重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂bean的细节,给上层应用带来了便利。从Spring 3.0开始, FactoryBean开始支持泛型,即接口声明改为FactoryBean<T>的形式:

在该接口中还定义了以下3个方法。

T getObject(): 返回由FactoryBean创建的bean实例,如果isSingleton()返回true, 则该实例会放到Spring容器中单实例缓存池中。

boolean isSingleton(): 返回由FactoryBean创建的bean实例的作用域是singleton还是prototype。

Class<T> getObjectType(): 返回FactoryBean创建的bean类型。

当配置文件中<bean>的class属性配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。例如:如果使用传统方式配置下面Car的<bean> 时,Car的每个属性分别对应一个<property>元素标签。

如果用FactoryBean的方式实现就会灵活一些,下例通过逗号分割符的方式一次性地为Car的所有属性指定配置值:

有了这个CarFactoryBean后,就可以在配置文件中使用下面这种自定义的配置方式配置CarBean了:

<bean id="car" class="com.test.factorybean.CarFactoryBean" carinfo="超级跑车,400,2000000"/>

当调用getBean(" car")时,Spring通过反射机制发现CarFactoryBean实现了FactoryBean的接口,这时Spring容器就调用接口方法CarFactoryBean/f.getObject()方法返回。如果希望获取Cm·FactoryBean的实例,则需要在使用getBean(beanName)方法时在beanName前显示的加上 "&"前缀,例如getBean(" &car")。

缓存中获取单例bean

介绍过FactoryBean的用法后,我们就可以了解bean加载的过程了。前面已经提到过,单例在Spring的同一个容器内只会被创建一次,后续再获取bean直接从单例缓存中获取,当然这里也只是尝试加载,首先尝试从缓存中加载,然后再次尝试尝试从singletonFactories中加载。因为在创建单例bean的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,Spring创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提早曝光加入到缓存中,一旦下一个bean创建时需要依赖上个bean,则直接使用ObjectFactory。

这个方法因为涉及循环依赖的检测,以及涉及很多变量的记录存取,所以让很多读者摸不着头脑。这个方法首先尝试从singletonObjects里面获取实例,如果获取不到再从earlySingleton­Objects里面获取,如果还获取不到,再尝试从singletonFactories里面获取beanName对应的ObjectFactory,然后调用这个ObjectFactory的getObject来创建bean,并放到earlySingleton­Objects里面去,并且从singletonFacotories里面remove掉这个Objectfactory,而对于后续的所有内存操作都只为了循环依赖检测时候使用,也就是在allowEarlyReference为true的清况下才会使用。

这里涉及用于存储bean的不同的map,可能让读者感到崩溃,简单解释如下。

从bean的实例中获取对象

在getBean方法中,getObjectForBeanlnstance是个高频率使用的方法,无论是从缓存中获得bean还是根据不同的scope策略加载bean。总之,我们得到bean的实例后要做的第一步就是调用这个方法来检测一下正确性,其实就是用于检测当前bean是否是FactoryBean类型的bean,如果是,那么需要调用该bean对应的FactoryBean实例中的getObject()作为返回值。

无论是从缓存中获取到的bean还是通过不同的scope策略加载的bean都只是最原始的bean状态,并不一定是我们最终想要的bean。举个例子,假如我们需要对工厂bean进行处理,那么这里得到的其实是工厂bean的初始状态,但是我们真正需要的是工厂bean中定义的factory-method方法中返回的bean,而getObjectForBeanInstance方法就是完成这个工作的。

从上面的代码来看,其实这个方法并没有什么重要的信息,大多是些辅助代码以及一些功能性的判断,而真正的核心代码却委托给了getObjectFromFactoryBean,我们来看看getObjectForBeanInstance中的所做的工作。

1 对FactoryBean正确性的验证。

2 对非FactoryBean不做任何处理。

3 对bean进行转换。

4 将从Factory中解析bean的工作委托给getObjectFromFactoryBean。

很遗憾,在这个代码中我们还是没有看到想要看到的代码,在这个方法里只做了一件事情,就是返回的bean如果是单例的,那就必须要保证全局唯一,同时,也因为是单例的,所以不必重复创建,可以使用缓存来提高性能,也就是说已经加载过就要记录下来以便于下次复用,否则的话就直接获取了。

在doGetObjectFromFactoryBean方法中我们终于看到了我们想要看到的方法,也就是object=factory.getObject(),是的,就是这句代码,我们的历程犹如剥洋葱一样,一层一层的直到最内部的代码实现,虽然很简单。

上面我们已经讲述了FactoryBean的调用方法,如果bean声明为FactoryBean类型,则当提取bean时提取的并不是FactoryBean,而是FactoryBean中对应的getObject方法返回的bean,而doGetObjectFromFactoryBean正是实现这个功能的。但是,我们看到在上面的方法中除了调用object = factory.getObject()得到我们想要的结果后并没有直接返回,而是接下来又做了些后处理的操作,这个又是做什么用的呢?于是我们跟踪进入AbstractAutowireCapableBeanFactory类的postProcessObjectFromFactoryBean方法:

对于后处理器的使用我们还未过多接触,后续章节会使用大量篇幅介绍,这里,我们只需了解在Spring获取bean的规则中有这样一条:尽可能保证所有bean初始化后都会调用注册的BeanPostProcessor的postProcessAfterlnitialization方法进行处理,在实际开发过程中大可以针对此特性设计自己的业务逻辑。

获取单例

之前我们讲解了从缓存中获取单例的过程,那么,如果缓存中不存在已经加载的单例bean就需要从头开始bean的加载过程了,而Spring中使用getSingleton的重载方法实现bean的加载过程。

上述代码中其实是使用了回调方法,使得程序可以在单例创建的前后做一些准备及处理操作,而真正的获取单例bean的方法其实并不是在此方法中实现的,其实现逻辑是在ObjectFactory类型的实例singletonFactory中实现的。而这些准备及处理操作包括如下内容。

1 检查缓存是否已经加载过。

2 若没有加载,则记录beanName的正在加载状态。

3 加载单例前记录加载状态。

可能你会觉得beforeSingletonCreation方法是个空实现,里面没有任何逻辑,但其实不是,这个函数中做了一个很重要的操作:记录加载状态,也就是通过this.singletonsCurrentlyln­Creation.add(beanName)将当前正要创建的bean记录在缓存中,这样便可以对循环依赖进行检测。

4 通过调用参数传入的ObjectFactory的个体Object方法实例化bean。

5 加载单例后的处理方法调用。

同步骤3的记录加载状态相似,当bean加载结束后需要移除缓存中对该bean的正在加载状态的记录。

6 将结果记录至缓存并删除加载bean过程中所记录的各种辅助状态。

7 返回处理结果。

虽然我们已经从外部了解了加载bean的逻辑架构,但现在我们还并没有开始对bean加载功能的探索,之前提到过,bean的加载逻辑其实是在传入的ObjectFactory类型的参数singletonFactory中定义的,我们反推参数的获取,得到如下代码:

准备创建bean

我们不可能指望在一个函数中完成一个复杂的逻辑,而且我们跟踪了这么多Spring代码,经历了这么多函数,或多或少也发现了一些规律:一个真正干活的函数其实是以do开头的,比如doGetObjectFromFactoryBean;而给我们错觉的函数,比如getObjectFromFactoryBean, 其实只是从全局角度去做些统筹的工作。这个规则对于createBean也不例外,那么让我们看看在createBean函数中做了哪些准备工作。

从代码中我们可以总结出函数完成的具体步骤及功能。

1 根据设置的class属性或者根据className来解析Class。

2 对override属性进行标记及验证。

很多读者可能会不知道这个方法的作用,因为在Spring的配置里面根本就没有诸如override-method之类的配置,那么这个方法到底是干什么用的呢?

其实在Spring中确实没有override-method这样的配置,但是如果读过前面的部分,可能会有所发现,在Spring配置中是存在lookup-method和replace-method的,而这两个配置的加载其实就是将配置统一存放在BeanDefinition中的methodOverrides属性里,而这个函数的操作其实也就是针对于这两个配置的。

3 应用初始化前的后处理器,解析指定bean是否存在初始化前的短路操作。

4 创建bean。

我们首先查看下对override属性标记及验证的逻辑实现。

处理override属性

查看源码中AbstractBeanDefinition类的prepareMethodOverrides方法:

通过以上两个函数的代码你能体会到它所要实现的功能吗?之前反复提到过,在Spring配置中存在lookup-method和replace-method两个配置功能,而这两个配置的加载其实就是将配置统一存放在BeanDefinition中的methodOverrides属性里,这两个功能实现原理其实是在bean实例化的时候如果检测到存在methodOverrides属性,会动态地为当前bean生成代理并使用对应的拦截器为bean做增强处理,相关逻辑实现在bean的实例化部分详细介绍。

但是,这里要提到的是,对于方法的匹配来讲,如果一个类中存在若干个重载方法,那么,在函数调用及增强的时候还需要根据参数类型进行匹配,来最终确认当前调用的到底是哪个函数。但是,Spring将一部分匹配工作在这里完成了,如果当前类中的方法只有一个,那么就设置重载该方法没有被重载,这样在后续调用的时候便可以直接使用找到的方法,而不需要进行方法的参数匹配验证了,而且还可以提前对方法存在性进行验证,正可谓一箭双雕。

实例化的前置处理

在真正调用doCreate方法创建bean的实例前使用了这样一个方法resolveBeforelnstantiation(beanName,mbd)对BeanDefinition中的属性做些前置处理。当然,无论其中是否有相应的逻辑实现我们都可以理解,因为真正逻辑实现前后留有处理函数也是可扩展的一种体现,但是,这并不是最重要的,在函数中还提供了一个短路判断,这才是最为关键的部分。

if (bean != null) {

      return bean;

当经过前置处理后返回的结果如果不为空,那么会直接略过后续的bean的创建而直接返回结果。这一特性虽然很容易被忽略,但是却起着至关重要的作用,我们熟知的AOP功能就是基于这里的判断的。

此方法中最吸引我们的无疑是两个方法applyBeanPostProcessorsBeforelnstantiation以及apply BeanPostProcessorsAfterInitialization。两个方法实现的非常简单,无非是对后处理器中的所有InstantiationAwareBeanPostProcessor类型的后处理器进行postProcessBeforelnstantiation方法和BeanPostProcessor的postProcessAfterlnitialization方法的调用。

1. 实例化前的后处理器应用

bean的实例化前调用,也就是将AbsractBeanDefinition转换为BeanWrapper前的处理。给子类一个修改BeanDefinition的机会,也就是说当程序经过这个方法后,bean可能已经不是我们认为的bean了,而是或许成为了一个经过处理的代理bean,可能是通过cglib生成的,也可能是通过其他技术生成的。我们只需要知道,在bean的实例化前会调用后处理器的方法进行处理。

2. 实例化后的后处理器应用

在讲解从缓存中获取单例bean的时候就提到过,Spring中的规则是在bean的初始化后尽可能保证将注册的后处理器的postProcessAfterlnitialization方法应用到该bean中,因为如果返回的bean不为空,那么便不会再次经历普通bean的创建过程,所以只能在这里应用后处理器的postProcessAfterInitialization方法。

循环依赖

实例化bean是一个非常复杂的过程,而其中比较难以理解的就是对循环依赖的解决,不管之前读者有没有对循环依赖方面的研究,这里有必要先对此知识点稍作回顾。

什么是循环依赖

循环依赖就是循环引用,就是两个或多个bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用,如图所示。

循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

Spring如何解决循环依赖

Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定义循环引用类:

在Spring中将循环依赖的处理分成了3种情况。

1. 构造器循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyln­CreationException异常表示循环依赖。

如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建TestC,最终在创建TestC时发现又需要TestA,从而形成一个环,没办法创建。

Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因此如果在创建bean过程中发现自己已经在“当前创建bean池”里时,将抛出BeanCurrently[nCreationException异常表示循环依赖;而对于创建完毕的bean将从“当前创建bean池”中清除掉。

我们通过一个直观的测试用例来进行分析。

针对以上代码的分析如下。

Srring容器创建"testA" bean,首先去“当前创建bean池“查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数"testB", 并将"testA"标识符 放到“当前创建bean池”。

Srring容器创建"testB" bean,首先去“当前创建bean池“查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数"testC", 并将"testB"标识符 放到“当前创建bean池”。

Srring容器创建"testC" bean,首先去“当前创建bean池“查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数"testA", 并将"testC"标识符 放到“当前创建bean池”。

到此为止Spring容器要去创建"testA" bean,发现该bean标识符在“当前创建bean池”中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。

2. setter循环依赖

表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean,如下代码所示:

具体步骤如下。

1. Spring容器创建单例"testA" bean,首先根据无参构造器创建bean,并暴露一个"ObjectFactory"用于返回一个提前暴露一个创建中的bean, 并将"testA"标识符放到“当前创建bean池”,然后进行setter注入"testB"。

2. Spring容器创建单例"testB" bean,首先根据无参构造器创建bean, 并暴露一个 "ObjectFactory"用于返回一个提前暴露一个创建中的bean, 并将"testB"标识符放到“当前创建bean池”,然后进行setter注入"circle"。

3. Spring容器创建单例"testC" bean,首先根据无参构造器创建bean,并暴露一个"ObjectFactory"用于返回一个提前暴露一个创建中的bean,并将"testC"标识符放到“当前创建bean池”,然后进行setter注入"testA"。进行注入"testA"时由于提前暴露了"ObjectFactory”工厂,从而使用它返回提前暴露一个创建中的bean。

4. 最后在依赖注入"testB"和"testA",完成setter注入。

3. prototype范围的依赖处理

对于"prototype"作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存"prototype"作用域的bean,因此无法提前暴露一个创建中的bean。示例如下:

对于"singleton"作用域bean,可以通过"setAllowCircularReferences(false);"来禁用循环引用。

创建bean

介绍了循环依赖以及Spring中的循环依赖的处理方式后,当经历过resolveBeforelnstantiation方法后,程序有两个选择,如果创建了代理或者说重写了InstantiationAwareBeanPostProcessor的postProcessBeforelnstantiation方法并在方法postProcess­Beforelnstantiation中改变了bean,则直接返回就可以了,否则需要进行常规bean的创建。而这常规bean的创建就是在doCreateBean中完成的。

尽管日志与异常的内容非常重要,但是在阅读源码的时候似乎大部分入都会直接忽略掉。

在此不深入探讨日志及异常的设计,我们看看整个函数的概要思路。

1 如果是单例则需要首先清除缓存。

2 实例化bean,将BeanDefinition转换为BeanWrapper。

转换是一个复杂的过程,但是我们可以尝试概括大致的功能,如下所示。

如果存在工厂方法则使用工厂方法进行初始化。

一个类有多个构造函数,每个构造函数都有不同的参数,所以需要根据参数锁定构造函数并进行初始化。

如果既不存在工厂方法也不存在带有参数的构造函数,则使用默认的构造函数进行bean的实例化。

3. MergedBeanDefinitionPostProcessor的应用。

bean合并后的处理,Autowired注解正是通过此方法实现诸如类型的预解析。

4 依赖处理。

在Spring中会有循环依赖的情况,例如,当A中含有B的属性,而B中又含有A的属性时就会构成一个循环依赖,此时如果A和B都是单例,那么在Spring中的处理方式就是当创建B的时候,涉及自动注入A的步骤时,并不是直接去再次创建A,而是通过放入缓存中的ObjectFactory来创建实例,这样就解决了循环依赖的问题。

5 属性填充。将所有属性填充至bean的实例中。

6 循环依赖检查。

之前有提到过,在Sping中解决循环依赖只对单例有效,而对于prototype的bean,Spring没有好的解决办法,唯一要做的就是抛出异常。在这个步骤里面会检测已经加载的bean是否已经出现了依赖循环,并判断是否需要抛出异常。

7 注册DisposableBean。

如果配置了destroy-method,这里需要注册以便于在销毁时候调用。

8 完成创建并返回。

可以看到上面的步骤非常的繁琐,每一步骤都使用了大量的代码来完成其功能,最复杂也是最难以理解的当属循环依赖的处理,在真正进入doCreateBean前我们有必要先了解下循环依赖。

创建bean的实例

当我们了解了循环依赖以后就可以深入分析创建bean的每一个步骤了,首先我们从createBeanInstance开始。

虽然代码中实例化的细节非常复杂,但是在createBeanlntance方法中我们还是可以清晰地看到实例化的逻辑的。

1 如果在RootBeanDefinition中存在factoryMethodName属性,或者说在配置文件中配置了factory-method,那么Spring会尝试使用instantiateUsingFactoryMethod(bean Name, mbd, args)方法根据RootBeanDefinition中的配置生成bean的实例。

2. 解析构造函数并进行构造函数的实例化。因为一个bean对应的类中可能会有多个构造函数,而每个构造函数的参数不同,Spring在根据参数及类型去判断最终会使用哪个构造函数进行实例化。但是,判断的过程是个比较消耗性能的步骤,所以采用缓存机制,如果已经解析过则不需要重复解析而是直接从RootBeanDefinition中的属性resolvedConstructorOrFactoryMethod缓存的值去取,否则需要再次解析,并将解析的结果添加至RootBeanDefinition中的属性resolvedConstructorOrFactoryMethod中。

AutowireConstructor

对于实例的创建Spring中分成了两种情况,一种是通用的实例化,另一种是带有参数的实例化。带有参数的实例化过程相当复杂,因为存在着不确定性,所以在判断对应参数上做了大量工作。

逻辑很复杂,函数代码量很大,不知道你是否坚持读完了整个函数并理解了整个功能呢?这里要先吐个槽,作者觉得这个函数的写法完全不符合Spring的一贯风格,如果你一直跟随作者的分析思路到这里,相信你或多或少对Spring的编码风格有所了解,Spring的一贯做法是将复杂的逻辑分解,分成N个小函数的嵌套,每一层都是对下一层逻辑的总结及概要,这样使得每一层的逻辑会变得简单容易理解。在上面的函数中,包含着很多的逻辑实现,作者觉得至少应该将逻辑封装在不同函数中而使得在AutowireConstructor中的逻辑清晰明了。

我们总览一下整个函数,其实现的功能考虑了以下几个方面。

1 构造函数参数的确定。

根据explicitArgs参数判断。

如果传入的参数explicitArgs不为空,那边可以直接确定参数,因为explicitArgs参数是在调用Bean的时候用户指定的,在BeanFactory类中存在这样的方法:

Object getBean(String name,Object ... args) throws BeansException;

在获取bean的时候,用户不但可以指定bean的名称还可以指定bean所对应类的构造函数或者工厂方法的方法参数,主要用于静态工厂方法的调用,而这里是需要给定完全匹配的参数的,所以,便可以判断,如果传入参数explictArgs不为空,则可以确定构造函数参数就是它。

缓存中获取。

除此之外,确定参数的办法如果之前已经分析过,也就是说构造函数参数已经记录在缓存中,那么便可以直接拿来使用。而且,这里要提到的是,在缓存中缓存的可能是参数的最终类型也可能是参数的初始类型,例如:构造函数参数要求的是int类型,但是原始的参数值可能是String类型的"1",那么即使在缓存中得到了参数,也需要经过类型转换器的过滤以确保参数类型与对应的构造函数参数类型完全对应。

配置文件获取。

如果不能根据传入的参数explicitArgs确定构造函数的参数也无法在缓存中得到相关信息,那么只能开始新一轮的分析了。

分析从获取配置文件中配置的构造函数信息开始,经过之前的分析,我们知道,Spring中配置文件中的信息经过转换都会通过BeanDefinition实例承载,也就是参数mbd中包含,那么可以通过调用mbd.getConstructorArgumentValues()来获取配置的构造函数信息。有了配置中的信息便可以获取对应的参数值信息了,获取参数值的信息包括直接指定值,如:直接指定构造函数中某个值为原始类型String类型,或者是一个对其他bean的引用,而这一处理委托给resolveConstructorArguments方法,并返回能解析到的参数的个数。

2. 构造函数的确定。

经过了第一步后已经确定了构造函数的参数,接下来的任务就是根据构造函数参数在所有构造函数中锁定对应的构造函数,而匹配的方法就是根据参数个数匹配,所以在匹配之前需要先对构造函数按照public构造函数优先参数数量降序、非public构造函数参数数量降序。这样可以在遍历的情况下迅速判断排在后面的构造函数参数个数是否符合条件。

由于在配置文件中并不是唯一限制使用参数位置索引的方式去创建,同样还支持指定参数名称进行设定参数值的情况,如<constructor-arg name="aa">,那么这种情况就需要首先确定构造函数中的参数名称。

获取参数名称可以有两种方式,一种是通过注解的方式直接获取,另一种就是使用Spring中提供的工具类ParameterNameDiscoverer来获取。构造函数、参数名称、参数类型、参数值都确定后就可以锁定构造函数以及转换对应的参数类型了。

3 根据确定的构造函数转换对应的参数类型。

主要是使用Spring中提供的类型转换器或者用户提供的自定义类拟转换器进行转换。

4 构造函数不确定性的验证。

当然,有时候即使构造函数、参数名称、参数类型、参数值都确定后也不一定会直接锁定构造函数,不同构造函数的参数为父子关系,所以Spring在最后又做了一次验证。

5 根据实例化策略以及得到的构造函数及构造函数参数实例化Bean。后面章节中将进行讲解。

instantiateBean

经历了带有参数的构造函数的实例构造,相信你会非常轻松愉快地理解不带参数的构造函数的实例化过程。

你会发现,此方法并没有什么实质性的逻辑,带有参数的实例构造中,Spring把精力都放在了构造函数以及参数的匹配上,所以如果没有参数的话那将是非常简单的一件事,直接调用实例化策略进行实例化就可以了。

实例化策略

实例化过程中反复提到过实例化策略,那这又是做什么用的呢?其实,经过前面的分析,我们已经得到了足以实例化的所有相关信息,完全可以使用最简单的反射方法直接反射来构造实例对象,但是Spring却并没有这么做。

看了上面两个函数后似乎我们已经感受到了Spring的良苦用心以及为了能更方便地使用Spring而做了大量的工作。程序中,首先判断如果beanDefinition.getMethodOverrides()为空也就是用户没有使用replace或者lookup的配置方法,那么直接使用反射的方式,简单快捷,但是如果使用了这两个特性,在直接使用反射的方式创建实例就不妥了,因为需要将这两个配置提供的功能切入进去,所以就必须要使用动态代理的方式将包含两个特性所对应的逻辑的拦截增强器设置进去,这样才可以保证在调用方法的时候会被相应的拦截器增强,返回值为包含拦截器的代理实例。

对于拦截器的处理方法非常简单,不再详细介绍。

记录创建bean的ObjectFactory

在doCreate函数中有这样一段代码:

这段代码不是很复杂,但是很多人不是太理解这段代码的作用,而且,这段代码仅从此函数中去理解也很难弄懂其中的含义,我们需要从全局的角度去思考Spring的依赖解决办法。

earlySingletonExposure:从字面的意思理解就是提早曝光的单例,我们暂不定义它的学名叫什么,我们感兴趣的是有哪些条件影响这个值。

mbd.isSingleton():没有太多可以解释的,此RootBeanDefinition代表的是否是单例。

this.allowCircularReferences:是否允许循环依赖,很抱歉,并没有找到在配置文件中如何配置,但是在AbstractRefreshableApplicationContext中提供了设置函数,可以通过硬编码的方式进行设置或者可以通过自定义命名空间进行配置,其中硬编码的方式代码如下。

ClassPathXmlApplicationContext bf= new ClassPathXmlApplicationContext ("aspectTest.Xml");

bf.setAllowBeanDefinitionOverriding(false);

isSingletonCurrently InCreation(beanName):该bean是否在创建中。在Spring中,会有个专门的属性默认为DefaultSingletonBean.Registry的singletonsCurrentlyinCreation来记录bean的加载状态,在bean开始创建前会将beanName记录在属性中,在bean创建结束后会将beanName从属性中移除。那么我们跟随代码一路走来可是对这个属性的记录并没有多少印象,这个状态是在哪里记录的呢?

不同scope的记录位置并不一样,我们以singleton为例,在singleton下记录属性的函数是在DefaultSingletonBeanRegistry类的public Object getSingleton(String beanName, ObjectFactory singletonfactory)函数的beforeSingletonCreation(beanName)和afterSingletonCreation(beanName)中,在这两段函数中分别this.singletonsCurrentlyInCreation.add(beanName)与this.singletonsCurrentlyln­Creation.remove(beanName)来进行状态的记录与移除。

经过以上分析我们了解变量earlySingletonExposure是否是单例、是否允许循环依赖、是否对应的bean正在创建的条件的综合。当这3个条件都满足时会执行addSingletonFactory操作,那么加入SingletonFactory的作用是什么呢?又是在什么时候调用呢?

我们还是以简单的AB循环依赖为例,类A中含有属性类B,而类B中又会含有属性类A,那么初始化beanA的过程如图所示。

图中展示了创建beanA的流程,图中我们看到,在创建A的时候首先会记录类A所对应的beanName,并将beanA的创建工厂加入缓存中,而在对A的属性填充也就是调用populate方法的时候又会再一次的对B进行递归创建。同样的,因为在B中同样存在A属性,因此在实例化B的的populate方法中又会再次地初始化B,也就是图形的最后,调用getBean(A)。关键是在这里,有心的同学可以去找找这个代码的实现方式,我们之前已经讲过,在这个函数中并不是直接去实例化A,而是先去检测缓存中是否有已经创建好的对应的bean,或者是否已经创建好的ObjectFactory,而此时对于A的ObjectFactory我们早已经创建,所以便不会再去向后执行,而是直接调用ObjectFactory去创建A。这里最关键的是ObjectFactory的实现。

在getEarlyBeanReference函数中并没有太多的逻辑处理,或者说除了后处理器的调用外没有别的处理工作,根据以上分析,基本可以理清Spring处理循环依赖的解决办法,在B中创建依赖A时通过ObjectFactory提供的实例化方法来中断A中的属性填充,使B中持有的A仅仅是刚刚初始化并没有填充任何属性的A,而这正初始化A的步骤还是在最开始创建A的时候进行的,但是因为A与B中的A所表示的属性地址是一样的,所以在A中创建好的属性填充自然可以通过B中的A获取,这样就解决了循环依赖的问题。

属性注入

在了解循环依赖的时候,我们曾经反复提到了populateBean这个函数,也多少了解了这个函数的主要功能就是属性填充,那么究竟是如何实现填充的呢?

在populateBean函数中提供了这样的处理流程。

1 InstantiationAwareBeanPostProcessor处理器的postProcessAfterinstantiation函数的应用,此函数可以控制程序是否继续进行属性填充。

2 根据注入类型(byName/byType),提取依赖的bean,并统一存入PropertyValues中。

3 应用InstantiationAwareBeanPostProcessor处理器的postProcessPropertyValues方法,对属性获取完毕填充前对属性的再次处理,典型应用是RequiredAnnotationBeanPostProcessor类中对属性的验证。

4 将所有PropertyValues中的属性填充至BeanWrapper中。

在上面的步骤中有几个地方是我们比较感兴趣的,它们分别是依赖注入(Autowire­ByName/AutowireByType)以及属性填充,那么,接下来进一步分析这几个功能的实现细节。

AutowireByName

上文提到根据注入类型(byName/byType),提取依赖的bean,并统一存入PropertyValues 中,那么我们首先了解下byName功能是如何实现的。

如果读者之前了解了Autowire的使用方法,相信理解这个函数的功能不会太困难,无非是在传入的参数PVS中找出已经加载的bean,并递归实例化,进而加入到PVS中。

AutowireByType

AutowireByType与AutowireByName对于我们理解与使用来说复杂程度都很相似,但是其实现功能的复杂度却完全不一样。

实现根据名称自动匹配的第一步就是寻找bw中需要依赖注入的属性,同样对于根据类型自动匹配的实现来讲第一步也是寻找bw中需要依赖注入的属性,然后遍历这些属性并寻找类型匹配的bean,其中最复杂的就是寻找类型匹配的bean。同时,Spring中提供了对集合的类型注入的支持,如使用注解的方式:

@Autowired
    private List<Test>  tests ;

Spring将会把所有与Test匹配的类型找出来并注入到tests属性中,正是由于这一因素,所以在AutowireByType函数中,新建了局部遍历AutowiredBeanNames,用于存储所有依赖的bean,如果只是对非集合类的属性注入来说,此属性并无用处。

对于寻找类型匹配的逻辑实现封装在了resolveDependency函数中。

寻找类型的匹配执行顺序时,首先尝试使用解析器进行解析,如果解析器没有成功解析,那么可能是使用默认的解析器没有做任何处理,或者是使用了自定义的解析器,但是对于集合等类型来说并不在解析范围之内,所以再次对不同类型进行不同情况的处理,虽说对于不同类型处理方式不一致,但是大致的思路还是很相似的,所以函数中只对数组类型进行了详细地注释。

applyPropertyValues

程序运行到这里,已经完成了对所有注入属性的获取,但是获取的属性是以PropertyValues形式存在的,还并没有应用到已经实例化的bean中,这一工作是在applyPropertyValues中。

初始化bean

大家应该记得在bean配置时bean中有一个init-method的属性,这个属性的作用是在bean实例化前调用init-method指定的方法来根据用户业务进行相应的实例化。我们现在就已经进入这个方法了,首先看一下这个方法的执行位置,Spring中程序已经执行过bean的实例化,并且进行了属性的填充,而就在这时将会调用用户设定的初始化方法。

虽然说此函数的主要目的是进行客户设定的初始化方法的调用,但是除此之外还有些其他必要的工作。

激活Aware方法

在分析其原理之前,我们先了解一下Aware的使用。Spring中提供一些Aware相关接口,比如BeanFactoryAware、ApplicationContextAware、ResourceLoaderAware、ServletContextAware等,实现这些Aware接口的bean在被初始之后,可以取得一些相对应的资源,例如实现BeanFactoryAware的bean在初始后,Spring容器将会注入BeanFactory的实例,而实现ApplicationContextAware的bean,在bean被初始后,将会被注入ApplicationContext的实例等。我们首先通过示例方法来了解一下Aware的使用。

按照上面的方法我们可以获取到Spring中BeanFactory,并且可以根据BeanFactory获取所有bean,以及进行相关设置。当然还有其他Aware的使用方法都大同小异,看一下Spring的实现方式,相信读者便会使用了。

代码简单得已经没有什么好说的了。读者可以自己尝试使用别的Aware,都比较简单。

处理器的应用

BeanPostProcessor相信大家都不陌生,这是Spring中开放式架构中一个必不可少的亮点,给用户充足的权限去更改或者扩展Spring,而除了BeanPostProcessor外还有很多其他的PostProcessor,当然大部分都是以此为基础,继承自BeanPostProcessor。BeanPostProcessor的使用位置就是这里,在调用客户自定义初始化方法前以及调用自定义初始化方法后分别会调用BeanPostProcessor的postProcessBeforelnitialization和postProcessAfterlnitialization方法,使用户可以根据自己的业务需求进行响应的处理。

激活自定义的init方法

客户定制的初始化方法除了我们熟知的使用配置init-method外,还有使自定义的bean实现InitializingBean接口,并在afterPropertiesSet中实现自己的初始化业务逻辑。

init-method与afterPropertiesSet都是在初始化bean时执行,执行顺序是afterPropertiesSet先执行,而init-method后执行。

在invokelnitMethods方法中就实现了这两个步骤的初始化方法调用。

注册DisposableBean

Spring中不但提供了对于初始化方法的扩展入口,同样也提供了销毁方法的扩展入口,对于销毁方法的扩展,除了我们熟知的配置属性destroy-method方法外,用户还可以注册后处理器DestructionAwareBeanPostProcessor来统一处理bean的销毁方法,代码如下:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值