Skip to content

Bean 是怎么加载和使用的,循环依赖怎么解决

1. Bean 的加载与使用:Bean 的生命周期

Spring Bean 的生命周期描述了一个 Bean 从一个简单的 Java 类定义,到成为一个功能完备、可供应用程序使用的对象所经历的全部过程。这个过程可以概括为以下几个关键阶段:

1.1 Bean 的定义 (Bean Definition)

这个阶段是容器的“准备阶段”。Spring 容器启动时,会读取应用程序提供的配置元数据。这些元数据可以来自于 XML 配置文件、Java 注解(如 @Component, @Service, @Repository)或 Java Config(@Configuration 类中的 @Bean 方法)。Spring 会解析这些元数据,为每个 Bean 创建一个对应的 BeanDefinition 对象。你可以把 BeanDefinition 看作是创建 Bean 的“配方”或“蓝图”,它包含了 Bean 的类名、作用域(singleton、prototype 等)、依赖关系、初始化方法等所有信息。

1.2 Bean 的实例化 (Instantiation)

当容器准备好 BeanDefinition 后,在某个时刻(对于单例 Bean 通常是容器启动时,对于原型 Bean 则是每次请求时),Spring 会根据 BeanDefinition 来创建 Bean 的实例。这个过程通常是通过 Java 的反射机制调用类的构造函数来完成的。此时得到的只是一个“裸露”的对象,它的属性都还是默认值,依赖也尚未注入。

1.3 Bean 的属性填充 (Population)

实例化完成后,Spring 会检查 BeanDefinition 中的依赖信息,并对 Bean 的属性进行填充。这就是我们熟知的“依赖注入”(DI)。Spring 会根据 @Autowired@Resource 等注解,或者 XML 配置,去容器中查找并注入该 Bean 所依赖的其他 Bean。

1.4 Bean 的初始化 (Initialization)

这是 Bean 生命周期中最为复杂、扩展点最多的一个阶段。在属性填充完毕后,Bean 还不能立即使用,需要经过一系列的初始化操作,使其达到可用的最终状态。这个过程大致包含以下步骤:

  • 执行 Aware 接口:如果 Bean 实现了如 BeanNameAwareBeanFactoryAwareApplicationContextAware 等接口,Spring 会调用相应的方法,将 Bean 的 ID、Bean 工厂、应用上下文等容器自身的信息注入给 Bean。

  • BeanPostProcessor 前置处理:Spring 会调用所有已注册的 BeanPostProcessorpostProcessBeforeInitialization 方法。这是一个非常重要的扩展点,允许我们对 Bean 进行自定义的加工。

  • 执行初始化方法:如果 Bean 实现了 InitializingBean 接口,Spring 会调用其 afterPropertiesSet 方法。如果 Bean 定义了 init-method(或使用 @PostConstruct 注解),该方法也会在此时被调用。

  • BeanPostProcessor 后置处理:最后,Spring 会调用所有 BeanPostProcessorpostProcessAfterInitialization 方法。Spring 的 AOP 功能,即为 Bean 创建代理对象,通常就是在这个步骤中完成的。经过这一步后返回的对象,可能已经不是原始实例,而是被代理过的对象了。

1.5 Bean 的就绪与使用 (Ready for Use)

经过完整的初始化阶段后,这个 Bean 就是一个功能完备的对象了。它被存放在 Spring 的单例缓存池中(对于单例 Bean 而言),随时可以被应用程序的其他部分注入和使用。

1.6 Bean 的销毁 (Destruction)

当 Spring 容器关闭时,它会负责销毁其管理的所有单例 Bean。销毁过程同样提供了回调机制:

  • 如果 Bean 实现了 DisposableBean 接口,Spring 会调用其 destroy 方法。

  • 如果 Bean 定义了 destroy-method(或使用 @PreDestroy 注解),该方法会被调用。

2. 循环依赖的解决方案

循环依赖是指两个或多个 Bean 互相持有对方的引用,形成了一个闭环,比如 A 依赖 B,同时 B 又依赖 A。这是一个在大型项目中可能遇到的棘手问题。

首先,需要明确 Spring 解决循环依赖的两个前提条件

  1. 只针对单例(Singleton)作用域的 Bean。对于原型(Prototype)作用域的 Bean,Spring 无法解决,会直接抛出异常。

  2. 只针对字段注入或 Setter 方法注入。对于构造函数注入,Spring 也无法解决,同样会抛出异常,因为对象的创建和依赖的注入耦合在了一起,无法解开。

Spring 解决循环依赖的核心,在于它内部精巧设计的三级缓存机制。

  • 一级缓存 singletonObjects:

    也叫“单例池”,用于存放已经完全初始化好的 Bean。这是一个 Map,里面的 Bean 可以直接使用。

  • 二级缓存 earlySingletonObjects:

    用于存放早期暴露的 Bean。这些 Bean 已经被实例化,但尚未完成属性填充和初始化。这个缓存的存在,是为了打破循环。一旦某个 Bean 被创建,它的一个早期引用就会被放入这个缓存,即使它还不完整。

  • 三级缓存 singletonFactories:

    这是一个工厂缓存,存放的是用于创建早期 Bean 的 ObjectFactory。这个 ObjectFactory 是一个函数式接口,当被调用时,它会返回一个 Bean 对象。三级缓存是解决 AOP 代理下循环依赖的关键。如果一个 Bean 需要被 AOP 代理,那么这个工厂返回的就是代理后的对象,否则返回原始对象。

2.1 解决流程(以 A 依赖 B,B 依赖 A 为例):

  1. 创建 AgetSingleton("A") 被调用。首先检查一级缓存,没有;检查二级缓存,没有。

  2. 实例化 A:Spring 发现无法从缓存获取,于是开始创建 A。它先通过反射实例化一个“裸露”的 A 对象。

  3. 早期暴露 A:为了解决可能的循环依赖,Spring 并不会立即去初始化 A,而是将一个能够获取 A 对象(或者 A 的代理对象)的工厂(ObjectFactory)放入三级缓存singletonFactories 中。

  4. 填充 A 的属性:Spring 开始为 A 注入属性,此时发现 A 依赖于 B。于是,Spring 会去获取 B,即调用 getSingleton("B")

  5. 创建 B:获取 B 的流程和 A 一样。检查一级、二级缓存,都没有。于是 Spring 开始创建 B,实例化一个“裸露”的 B 对象,并将其对应的 ObjectFactory 也放入三级缓存

  6. 填充 B 的属性:Spring 为 B 注入属性,此时发现 B 依赖于 A。于是,Spring 再次去获取 A,即调用 getSingleton("A")

  7. 打破循环的关键点

    • 这一次获取 A,Spring 在一级缓存中找不到(因为 A 还没完全初始化)。

    • 在二级缓存中也找不到。

    • 但是,它在三级缓存中找到了用于创建 A 的 ObjectFactory

    • Spring 会调用这个工厂的 getObject() 方法。此时,如果 A 需要 AOP 代理,这个工厂就会返回 A 的代理对象;如果不需要,就返回原始的 A 对象。

    • 然后,这个早期暴露的 A 对象被放入二级缓存earlySingletonObjects 中,并从三级缓存中移除对应的工厂。

    • 这个早期暴露的 A 对象被返回,并成功注入到 B 的属性中。

  8. 完成 B 的创建:B 的依赖被成功注入,接着 B 完成后续的初始化过程。初始化完成后,完整的 B 对象被放入一级缓存singletonObjects 中,并从二级、三级缓存中移除。

  9. 完成 A 的创建:现在,B 对象已经可以被获取了。Spring 将这个完整的 B 对象注入到之前等待的 A 对象中。A 的依赖也全部注入完毕,接着 A 完成后续的初始化过程。最终,完整的 A 对象也被放入一级缓存

至此,A 和 B 的循环依赖被成功解决,两者都成为了功能完备的 Bean。这个过程的核心就是通过二级和三级缓存,提前暴露了一个“半成品”的 Bean 引用,从而打破了循环等待的僵局。