§ 一个排列组合问题
假设存在如下的配置类:
|
Spring在实例化experienceCenterClient bean对象时,需要先实例化ExperienceCenterConfig,所以就出现了experienceCenterClient → ExperienceCenterConfig的依赖关系。@Resource依赖注入是在初始化bean对象时调用的,因此同时出现了ExperienceCenterConfig→ThriftServerAddressProvider的依赖关系。综合来看,上面这个类的依赖关系大致是:experienceCenterClient → ExperienceCenterConfig → ThriftServerAddressProvider。
假设ThriftServerAddressProvider的初始化类的代码如下:
|
类比上面的分析,RpcEdsConfig也会出现这样的依赖关系:thriftServerAddressEdsManager → RpcEdsConfig → HealthCheckReporter(defaultHealthCheckMetricsReporter)。
现在的问题来了:假设在实例化thriftServerAddressEdsManager时,容器中已经存在且仅存在一个名称为defaultHealthCheckMetricsReporter,类型是HealthCheckReporter的bean对象,那么thriftServerAddressEdsManager这个bean对象是不是可以直接被实例化到容器中?
答案是看情况,有可能可以,也有可能不可以。虽然Spring容器中,当前已经存在且仅存在一个名称为defaultHealthCheckMetricsReporter,类型是HealthCheckReporter的bean对象,但并不能代表整个容器在实例化完成之后,一定仅存在一个类型为HealthCheckReporter的bean。
这里主要的原因就在于FactoryBean的定义:
|
如果我们不使用泛型,则getObject()的返回类型是Object,此时只有在运行时才能知道FactoryBean具体的返回类型。那Spring在解决依赖注入的时候,是如何确定当前需要注入的bean对象具体是哪个呢?
从Spring的源码来看:任何bean对象在依赖注入时,Spring都会遍历当前所有还没有创建的FactoryBean对象,尝试创建这些FactoryBean对象,以便于根据FactoryBean的getObject/getObjectType方法确认这些FactoryBean是否会返回依赖注入需要的类型的bean。
那这里就有一个非常有意思的逻辑了。
假设我们存在一系列的类似如下结构的@Configuration类:
|
大致就是每个@Configuration都包含一个FactoryBean(FactoryBean类型不一定需要一致),然后每个@Configuration又都依赖注入了一个bean对象(Bean对象也不一定需要一致)。我们按照Spring加载BeanDefinition的顺序,给这一系列的@Configuration排个序:
C1,C2,C3,... Cn
那么Spring在实例化C1时,大致会这么做:
- 在实例化C1的时候,由于存在依赖注入,因此需要遍历C2,C3,...,Cn,以实例化C2,C3,...,Cn中的FactoryBean对象,确认FactoryBean返回的对象是不是C1依赖注入需要的类型,这里首先会处理C2
- 在实例化C2的时候,由于存在依赖注入,因此需要遍历C3,...,Cn,以实例化C3,...,Cn中的FactoryBean对象,确认FactoryBean返回的对象是不是C2依赖注入需要的类型,这里首先会处理C3
- ...
- 在实例化Cn的时候,虽然依旧需要处理依赖注入,但此时已经不存在需要实例化的FactoryBean了,因此Spring开始尝试实例化Cn
所以上述这一遍跑下来,在内存中,大致会构建出如下这样的实例化依赖关系链:
- C1 → C2 → C3 → ... → Cn
下面,我们从上述场景出发,分最差情形和最好情形分别讨论:
€ 最差情形
- 对于Cn:
Cn在实例化时,需要将@Resource注解依赖注入的对象赋值给新创建的类型为Cn的bean。假设此时实例化出现了异常,则Cn会因为依赖注入失败抛出初始化异常。那么对于Cn-1 → Cn这段依赖关系的上游Cn-1来说,就认为Cn中的FactoryBean不会返回Cn-1实例化时需要注入的bean;
- 对于Cn-1:
由于Cn并没有返回Cn-1初始化时需要的bean对象,此时的Cn-1的处理逻辑就等同于末尾的Cn了。假设依旧出现了异常,则Cn-1也会因为依赖注入失败抛出初始化异常。那么对于Cn-2 → Cn-1的这段依赖关系的上游Cn-2来说,同样认为Cn-1的FactoryBean不会返回Cn-2实例化时需要注入的bean;
- 对于Cn-2:
前半部分的处理逻辑和Cn-1是相同的,Cn-2会发现Cn-1并没有返回它实例化需要注入的bean。但是,由于Cn-1之后还有一个Cn没有检查,所以Cn-2会去调用Cn,尝试实例化Cn,确认是否能得到自己依赖注入需要的bean;
- 回到Cn:
Cn-2调用Cn时,Cn首先需要确认自己依赖注入的bean对象是什么。此时,由于Cn-1因为回退(Cn → Cn-1 → Cn-2)已经从依赖链中退出,因此Cn会调用Cn-1,尝试确认Cn-1的FactoryBean是否会返回Cn依赖注入需要的bean;
经过上面一轮处理,形成了新的实例化依赖关系链:
- C1 → C2 → C3 → ... → Cn-2 → Cn → Cn-1
依次分析,后面几次的调用链路分别会是:
- C1 → C2 → C3 → ... → Cn-3 → Cn-1 → Cn-2 → Cn
- C1 → C2 → C3 → ... → Cn-3 → Cn-1 → Cn → Cn-2
- C1 → C2 → C3 → ... → Cn-3 → Cn → Cn-2 → Cn-1
- ...
这个过程看起来是对n个数的全排列,时间复杂度是n!。因此,上述例子在最坏的情形下需要执行n!次,这是比指数复杂度还高的复杂度。凉凉...
€ 最好情形
- 对于Cn:
处理Cn时,由于已经没有还未创建(所有的FactoryBean要么已经创建好了,要么就处于创建过程中)的FactoryBean,因此Cn开始实例化自身。假设此时容器中刚好存在一个满足依赖注入的bean,则Cn实例化成功。Cn实例化成功后,便可以调用Cn中的FactoryBean方法创建对应的FactoryBean。FactoryBean创建完成后,便可以获得该FactoryBean实际返回的bean类型了;
- 对于Cn-1:
Cn中的FactoryBean对象返回的bean类型,可能是Cn-1需要的类型,也可能不是Cn-1需要的类型。但此时,只要容器中能找到符合条件bean(无论是不是因为Cn生成的)注入到Cn-1中,则Cn-1就可以实例化完成,后面的操作和Cn类似
- ...
- 对于C1:
处理过程也是相似的,如果没有错误,最终会正确完成实例化。
因此,在这种情况下,上述的所有bean的实例化只用了一次,时间复杂度是常数。