关于Spring容器启动过程中,因 FactoryBean导致的循环依赖的问题分析

本文探讨Spring框架中配置类的依赖关系,涉及FactoryBean与依赖注入的交互,实例化过程中的递归调用和时间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

§ 一个排列组合问题

假设存在如下的配置类:

@Configuration

public class ExperienceCenterConfig {

    @Resource

    private ThriftServerAddressProvider thriftServerAddressEdsManager;

 

    @Bean

    public ThriftServiceClientProxyFactory experienceCenterClient() {

        ThriftServiceClientProxyFactory factory = new ThriftServiceClientProxyFactory();

        factory.setThriftClass(ExperienceCenterService.class);

        factory.setSocketTimeout(5000);

        factory.setConnectTimeout(5000);

        factory.setServerAddressProvider(thriftServerAddressEdsManager);

        PrivateEnvTagRouter router = new PrivateEnvTagRouter();

        factory.setTagRouter(router);

        return factory;

    }

}

Spring在实例化experienceCenterClient bean对象时,需要先实例化ExperienceCenterConfig,所以就出现了experienceCenterClient → ExperienceCenterConfig的依赖关系。@Resource依赖注入是在初始化bean对象时调用的,因此同时出现了ExperienceCenterConfig→ThriftServerAddressProvider的依赖关系。综合来看,上面这个类的依赖关系大致是:experienceCenterClient → ExperienceCenterConfig → ThriftServerAddressProvider。

假设ThriftServerAddressProvider的初始化类的代码如下:

@Configuration

public class RpcEdsConfig {

    @Value("${eds.hosts:#{null}}")

    private String hosts;

 

    @Autowired(required = false)

    @Qualifier("defaultHealthCheckMetricsReporter")

    private HealthCheckReporter healthCheckReporter;

 

    @Bean

    @Lazy

    public ThriftServerAddressEdsManager thriftServerAddressEdsManager() {

        if (StringUtils.isEmpty(this.hosts)) {

            throw new IllegalArgumentException("eds.hosts not configured");

        else {

            ThriftServerAddressEdsManager thriftServerAddressEdsManager = new ThriftServerAddressEdsManager(this.hosts, this.healthCheckReporter);

            return thriftServerAddressEdsManager;

        }

    }

}

类比上面的分析,RpcEdsConfig也会出现这样的依赖关系:thriftServerAddressEdsManager → RpcEdsConfig → HealthCheckReporter(defaultHealthCheckMetricsReporter)。

现在的问题来了:假设在实例化thriftServerAddressEdsManager时,容器中已经存在且仅存在一个名称为defaultHealthCheckMetricsReporter,类型是HealthCheckReporter的bean对象,那么thriftServerAddressEdsManager这个bean对象是不是可以直接被实例化到容器中?

答案是看情况,有可能可以,也有可能不可以。虽然Spring容器中,当前已经存在且仅存在一个名称为defaultHealthCheckMetricsReporter,类型是HealthCheckReporter的bean对象,但并不能代表整个容器在实例化完成之后,一定仅存在一个类型为HealthCheckReporter的bean。

这里主要的原因就在于FactoryBean的定义:

public interface FactoryBean<T> {

    @Nullable

    T getObject() throws Exception;

 

    @Nullable

    Class<?> getObjectType();

    default boolean isSingleton() {

        return true;

    }

}

如果我们不使用泛型,则getObject()的返回类型是Object,此时只有在运行时才能知道FactoryBean具体的返回类型。那Spring在解决依赖注入的时候,是如何确定当前需要注入的bean对象具体是哪个呢?

从Spring的源码来看:任何bean对象在依赖注入时,Spring都会遍历当前所有还没有创建的FactoryBean对象,尝试创建这些FactoryBean对象,以便于根据FactoryBean的getObject/getObjectType方法确认这些FactoryBean是否会返回依赖注入需要的类型的bean

那这里就有一个非常有意思的逻辑了。

假设我们存在一系列的类似如下结构的@Configuration类:

@Configuration

public class SomeConfiguration {

    @Resource

    private SomeDependencyBean dependencyBean;

 

    @Bean

    public SomeFactoryBean someFactoryBean() {

        // Some Logic...

        return someFactoryBean;

    }

}

大致就是每个@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的实例化只用了一次,时间复杂度是常数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值