Spring IOC容器:Bean生命周期与循环依赖解决

Spring IOC容器:Bean生命周期与循环依赖解决

引言

在Java企业级开发领域,Spring框架无疑占据了统治地位,而控制反转作为Spring的核心思想,贯穿了框架的始终。对于大多数开发者而言,日常工作中只需通过@Autowired注解即可完成依赖注入,似乎一切都很自然。然而,当面试官问起:“Spring是如何解决循环依赖的?”或者“Bean在实例化过程中经历了哪些步骤?”时,很多人往往只能给出模糊的答案。

深入理解Bean的生命周期与循环依赖机制,不仅仅是为了应对面试,更是为了在遇到诸如“Bean初始化顺序异常”、“依赖注入失败”等棘手问题时,能够迅速定位根源。本文将从源码级别剖析Spring IOC容器的内部运作机制,通过可运行的代码示例,带你彻底搞懂这两大核心技术难点。

核心概念:Bean的生命周期

Spring Bean的生命周期是指Bean从创建到销毁的整个过程。相比于普通的Java对象,Spring Bean的生命周期要复杂得多,因为Spring赋予了它诸如依赖注入、Aware接口回调、初始化前后的增强等能力。

简而言之,Bean的生命周期可以概括为四大阶段:实例化 -> 属性赋值 -> 初始化 -> 销毁

1. 实例化

这是Bean生命周期的第一步。Spring通过构造器反射创建Bean的实例。此时对象已经存在于内存中,但所有属性均为默认值,尚未进行依赖注入。

2. 属性赋值

实例化完成后,Spring会根据配置或注解(如@Autowired),将相关的依赖注入到Bean实例中。如果在此过程中发现依赖的对象尚未创建,则会递归地创建依赖对象。

3. 初始化

这是生命周期中最复杂、扩展性最强的一个阶段,主要包含以下几个步骤:
* Aware接口回调:如果Bean实现了BeanNameAwareApplicationContextAware等接口,Spring会将Bean的名称或容器上下文注入给Bean。
* BeanPostProcessor(前置处理):执行postProcessBeforeInitialization方法。这是Spring留给开发者的扩展点,著名的@PostConstruct注解就是在此阶段通过InitDestroyAnnotationBeanPostProcessor处理的。
* InitializingBean与init-method:如果Bean实现了InitializingBean接口,会调用afterPropertiesSet()方法;如果定义了init-method,也会在此执行。
* BeanPostProcessor(后置处理):执行postProcessAfterInitialization方法。这是Spring AOP代理创建的关键节点,如果Bean需要被代理(如事务管理),Spring会在此处用代理对象替换原始对象。

4. 销毁

当容器关闭时,会执行销毁逻辑。主要包括@PreDestroy注解方法、DisposableBean接口的destroy()方法以及定义的destroy-method

技术原理:循环依赖解决机制

循环依赖是指Bean A依赖Bean B,而Bean B又依赖Bean A(或更长的闭环)。在单例模式下,Spring通过三级缓存机制巧妙地解决了构造器注入之外的循环依赖问题。

三级缓存架构

Spring在DefaultSingletonBeanRegistry类中维护了三个Map结构:

  1. 一级缓存Map<String, Object> singletonObjects
    • 存放已经完全初始化好的Bean(Complete Bean)。
  2. 二级缓存Map<String, Object> earlySingletonObjects
    • 存放早期暴露的Bean,即实例化完成但尚未初始化的Bean(Early Bean)。用于解决循环依赖。
  3. 三级缓存Map<String, ObjectFactory<?>> singletonFactories
    • 存放ObjectFactory工厂对象。它的作用是提前暴露Bean的引用,并且如果该Bean需要被AOP代理,这里返回的是代理对象,而非原始对象。

解决流程演示

假设存在循环依赖:A依赖B,B依赖A。

  1. 创建A:Spring开始创建Bean A。
  2. A实例化:A被实例化,但属性尚未注入。
  3. A暴露引用(三级缓存):Spring将A的工厂对象放入三级缓存中。
  4. A注入B:A开始填充属性,发现依赖B,于是尝试获取B。
  5. 创建B:B不存在,Spring开始创建Bean B。
  6. B实例化:B被实例化。
  7. B注入A:B开始填充属性,发现依赖A,于是尝试获取A。
  8. 获取A
    • B先查一级缓存,没有。
    • B再查二级缓存,没有。
    • B查三级缓存,发现A的工厂对象!调用工厂对象的getObject()方法获取A的早期引用(可能是代理对象),并将其放入二级缓存,同时从三级缓存移除。
  9. B完成创建:B拿到了A的引用,完成属性填充和初始化,放入一级缓存。
  10. A完成创建:A继续执行,拿到创建好的B,完成属性填充和初始化,放入一级缓存。

核心问题:为什么需要三级缓存?
如果仅仅是解决循环依赖,二级缓存其实足够。三级缓存的存在主要是为了处理AOP代理。如果在创建过程中发现Bean需要被代理(例如加了事务注解),Spring必须在循环依赖发生时,提前生成代理对象。三级缓存中的ObjectFactory会根据情况返回原始对象或代理对象,确保注入到其他Bean中的引用是最终正确的代理对象。

实战代码:自定义Bean生命周期与循环依赖验证

下面通过一段完整的Java代码,演示Bean的生命周期流程以及循环依赖的自动解决。

1. 定义生命周期Bean

```java
package com.example.springdemo;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/*
* 演示Spring Bean生命周期的自定义Bean
* 实现了Aware接口、InitializingBean、DisposableBean
/
public class LifecycleBean implements BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean {

private String name;

// 1. 构造器实例化
public LifecycleBean() {
    System.out.println("1. [构造器] Bean实例化:LifecycleBean Constructor called.");
}

// 2. 属性注入
public void setName(String name) {
    System.out.println("2. [属性注入] Setting name: " + name);
    this.name = name;
}

// 3. BeanNameAware接口回调
@Override
public void setBeanName(String s) {
    System.out.println("3. [Aware接口] BeanNameAware called, bean name
posted @ 2026-02-26 17:01  寒人病酒  阅读(2)  评论(0)    收藏  举报