☰
Spring IOC 容器原理详解:控制反转、依赖注入、Bean 生命周期
2026/10/15 13:32:59 网站建设 项目流程

IOC(控制反转)是 Spring 框架的基石,SpringAOP、Spring 事务、SpringBoot 自动配置全部建立在 IOC 容器之上。很多后端开发者每天都在写@Service、@Autowired,但对 IOC 底层运行机制、Bean 完整生命周期、循环依赖的底层解决方案理解很浅。本文从设计思想、核心接口、依赖注入方式、Bean 完整生命周期、循环依赖源码思路,完整拆解 IOC 容器,看完彻底吃透 Spring IOC。

前言

Spring 两大核心支柱:IOC 容器与AOP 面向切面编程。 在没有 Spring 的年代,我们创建对象需要手动new,对象之间的依赖关系硬编码写死。如果要替换一个实现类,需要修改多处代码,耦合度极高,单元测试也很难 Mock 依赖对象。

IOC 思想的出现就是为了解决对象耦合问题:把对象的创建、销毁、依赖装配、生命周期管理全部交给 Spring 容器。开发者不再主动去 new 对象,控制权交给容器,这就是控制反转 Inversion of Control。 而DI(依赖注入),是实现 IOC 思想的具体手段。

总结:IOC 是思想,DI 是落地实现方式。

一、IOC 核心概念:控制反转 & 依赖注入 DI

1.1 什么是控制反转(IOC)

传统开发方式:开发者主动控制。

// 自己new对象,自己维护依赖 UserService userService = new UserService(); UserDao userDao = new UserDao(); userService.setUserDao(userDao);

对象创建、依赖装配的控制权,掌握在程序员代码里。

IOC 模式:容器控制。 我们只需要告诉 Spring,哪些类需要交给容器管理(@Component/@Service/@Repository/@Controller),由 Spring 容器扫描类、实例化对象、自动装配依赖,开发者直接从容器获取 Bean 使用。 对象生命周期的控制权,从开发者反转到 Spring 容器。

1.2 依赖注入 DI(Dependency Injection)

容器自动把当前 Bean 所需要的依赖对象注入进来,不需要手动实例化依赖。Spring 提供 3 种主流注入方式:

  1. 构造器注入
@Service public class UserService{ private final UserDao userDao; //构造器注入 public UserService(UserDao userDao){ this.userDao = userDao; } }

优点:依赖不可变、保证依赖不为空,方便单元测试,Spring4.3 之后单构造器无需加@Autowired。

  1. Setter 方法注入
@Autowired public void setUserDao(UserDao userDao){ this.userDao = userDao; }

适合依赖可选、运行时动态修改依赖场景。

  1. 字段注入(@Autowired 直接写在成员变量上)
@Autowired private UserDao userDao;

写法简洁,但缺点明显:无法声明为 final、不能脱离 IOC 容器做单元测试,不推荐生产项目大量使用。

开发规范建议:强制依赖使用构造器注入,可选依赖使用 Setter 注入,尽量避免字段注入。

二、IOC 容器核心接口体系

Spring IOC 容器顶层接口是BeanFactory,ApplicationContext是 BeanFactory 的子接口,也是我们开发中最常用的容器。

BeanFactory

Spring 最底层 IOC 容器,懒加载 Bean:只有调用getBean()的时候,才会实例化对象。只提供基础 Bean 管理能力,功能比较单薄。

ApplicationContext(应用上下文)

BeanFactory 的增强子接口,我们项目实际使用的容器。

  • 启动时预实例化单例 Bean(非懒加载),容器启动就能提前发现 Bean 创建异常;
  • 额外能力:资源加载、国际化、事件发布机制、AOP 支持、环境变量解析; 常见实现类:
  • ClassPathXmlApplicationContext:xml 配置方式
  • AnnotationConfigApplicationContext:注解驱动(SpringBoot 底层就是这个)

面试考点:BeanFactory 和 ApplicationContext 区别 BeanFactory 懒加载;ApplicationContext 启动时加载单例 Bean,功能更丰富。ApplicationContext 继承 BeanFactory。

三、BeanDefinition:Bean 的元数据模型

Spring 不会直接拿着 Class 类去创建对象,而是先把被注解标记的类解析成BeanDefinition。 BeanDefinition 是 Bean 的 “描述模板”,保存类的全部信息:

  • class 全限定名
  • 作用域:singleton 单例 /prototype 原型
  • 懒加载标识
  • 初始化方法、销毁方法
  • 依赖信息

流程:扫描类 → 生成 BeanDefinition → 根据 BeanDefinition 实例化 Bean。 可以理解:BeanDefinition 是图纸,Bean 是根据图纸造出来的实例。

四、Bean 完整生命周期

完整生命周期分为:实例化 → 属性填充 → 初始化 → 使用 → 销毁

  1. 资源扫描与解析容器启动,扫描指定包下带有@Component等注解的类,解析生成 BeanDefinition,存入 BeanDefinitionMap。
  2. 实例化 Instantiation通过 Java 反射机制,调用构造方法,new 出原始对象。

此时仅仅是一块空对象,成员变量还没有赋值。

  1. 属性填充 PopulateBean(DI 依赖注入)Spring 解析@Autowired等注解,从容器找到依赖 Bean,给对象的成员变量赋值,完成依赖注入。

循环依赖就发生在这一步。

  1. Aware 接口回调(感知容器)如果 Bean 实现了一系列 Aware 接口,容器回调方法,让 Bean 拿到容器资源:
  • BeanNameAware:拿到 Bean 在容器中的名字
  • BeanFactoryAware:拿到 BeanFactory 对象
  • ApplicationContextAware:拿到 ApplicationContext 上下文(最常用)
  1. BeanPostProcessor 前置处理(Bean 后置处理器)在初始化方法执行之前,执行 BeanPostProcessor#postProcessBeforeInitialization。

AOP 代理对象就是在这里判断并生成代理。

  1. 初始化 Initialization按顺序执行: ①@PostConstruct注解标记方法(Java 规范) ② InitializingBean#afterPropertiesSet () 接口回调 ③ xml 配置的 init-method 自定义初始化方法
  2. BeanPostProcessor 后置处理执行 postProcessAfterInitialization。

重点:AOP 如果需要创建代理对象,就在这一步完成。如果需要代理,返回代理对象;不需要代理,返回原始对象。

  1. 单例 Bean 放入单例池 singletonObjects初始化完成的完整 Bean,存入ConcurrentHashMap单例池,对外提供使用。

singletonObjects 就是我们常说的一级缓存。

  1. Bean 使用阶段Bean 已经就绪,业务代码可以直接注入、调用。
  2. 销毁 Destruction容器关闭时执行销毁逻辑(仅单例 Bean 会由容器管理销毁,原型 Bean 容器不管理销毁) 执行顺序: ①@PreDestroy注解方法 ② DisposableBean#destroy () 接口回调 ③ xml 配置 destroy-method 自定义销毁方法

记忆顺序:实例化 → 填充属性 → Aware 回调 → 前置后置处理器 → @PostConstruct → InitializingBean → init-method → 放入单例池使用 → 销毁。

五、Bean 作用域详解

Spring 支持多种 Bean 作用域,面试常问前两个:

  1. singleton(默认):单例。整个 IOC 容器只创建 1 个 Bean 实例,存入单例池。容器启动创建(非懒加载)。
  2. prototype:原型。每次调用getBean()都会新建一个对象,容器不管理原型 Bean 的销毁。
  3. request:Web 环境,一次 http 请求一个实例
  4. session:Web 环境,一个 session 一个实例
  5. application:Web 环境,全局 ServletContext 级别单例

坑点:单例 Bean 中如果注入原型 Bean,原型 Bean 不会每次都新建,因为注入只发生一次,需要通过 ApplicationContext 手动 getBean 获取原型实例。

六、循环依赖底层原理

6.1 什么是循环依赖

A 类依赖 B,B 类同时依赖 A。容器创建 A 的时候发现需要 B,去创建 B;创建 B 的时候发现需要 A,A 还没完全创建完成。

@Component public class A{ @Autowired private B b; } @Component public class B{ @Autowired private A a; }

6.2 Spring 如何解决单例 setter 注入循环依赖?三级缓存

Spring 用三级缓存解决单例、setter 注入场景下的循环依赖:

//一级缓存:存放完全初始化好,可以直接使用的单例Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); //二级缓存:实例化完成,但还没填充属性的早期Bean对象(半成品Bean) private final Map<String, Object> earlySingletonObjects = new HashMap<>(); //三级缓存:Bean工厂,用来生成早期对象引用,支持AOP代理 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
执行流程:
  1. 创建 A,实例化得到原始对象,开始填充属性,发现依赖 B;
  2. 开始创建 B,实例化原始对象,填充属性发现依赖 A;
  3. A 还没完成初始化,但是 A 实例化已经完成,把 A 的 ObjectFactory 放入三级缓存 singletonFactories;
  4. B 去三级缓存拿到 A 的早期引用(半成品 A),完成 B 的属性填充、初始化;
  5. B 完整创建完成,放入一级缓存;回到 A,注入 B 对象;A 完成属性填充初始化,A 移入一级缓存;
  6. 删除二级、三级缓存中 A 的临时记录。

6.3 不能解决循环依赖的场景

  1. 原型 prototype 作用域 Bean:无法解决,原型 Bean 不使用三级缓存;
  2. 构造器注入的循环依赖:无法解决,实例化阶段就需要依赖对象,还没机会放入三级缓存;
  3. AOP 代理 + 循环依赖,三级缓存的 ObjectFactory 就是用来生成代理对象,这个场景可以支持。

总结:Spring 只能解决单例、setter / 字段注入场景循环依赖;构造注入、原型 Bean 循环依赖直接抛出异常。

七、IOC 容器带来的工程价值

  1. 解耦:对象之间不再硬编码依赖,更换实现类不需要修改业务代码,面向接口编程。
  2. 统一管理 Bean 生命周期:创建、初始化、销毁由容器统一管控。
  3. 天然支持 AOP 增强:IOC 把 Bean 统一托管,才有机会生成代理对象,实现事务、切面等能力。
  4. 单元测试友好:依赖可以轻松 Mock,不需要手动 new 一整条依赖链。
  5. 集中配置管理:配合注解、配置文件,统一管理对象创建逻辑。

八、实战开发踩坑总结

  1. 单例 Bean 是容器全局唯一,不要在 Bean 中定义可变成员变量,会出现线程安全问题;
  2. 原型 Bean 不要存到全局静态变量,会导致原型失效;
  3. @PostConstruct是初始化方法,构造器是实例化,不要混淆;构造器执行时依赖还没注入完成;
  4. 循环依赖优先改成设计层面解耦,不要单纯依赖三级缓存兜底,业务上循环依赖本身就是设计问题。

九、小测

Q1:IOC 和 DI 的区别?

A:IOC 是控制反转的设计思想,把对象控制权交给容器;DI 依赖注入是 IOC 的实现手段,容器自动装配对象依赖。

Q2:BeanFactory 和 ApplicationContext 区别?

A:BeanFactory 是底层接口,懒加载 Bean;ApplicationContext 是子接口,预加载单例 Bean,提供国际化、事件发布等扩展能力,开发中常用。

Q3:Bean 生命周期顺序,@PostConstruct 执行时机?

A:实例化 → 属性填充 → Aware 回调 → BeanPostProcessor 前置处理 → @PostConstruct → InitializingBean → init-method → 后置处理器 → 放入单例池使用。

Q4:三级缓存分别作用?为什么需要三级,两级行不行?

A:一级:完整 Bean;二级:半成品 Bean;三级:对象工厂,用来生成 AOP 代理对象。如果没有三级缓存,循环依赖场景下 AOP 代理无法生成,两级缓存只能处理普通无代理 Bean。

Q5:哪些场景循环依赖 Spring 无法解决?

A:原型 Bean、构造器注入产生的循环依赖。

结语

IOC 是 Spring 体系最底层的地基,AOP、声明式事务、SpringBoot 自动配置全部构建在 IOC 容器之上。很多开发停留在会用@Autowired,但是理解 Bean 创建流程、三级缓存原理之后,遇到线上 Bean 创建异常、循环依赖报错、AOP 失效问题,才能快速定位根因。 掌握 IOC 之后,再去学习 AOP,理解难度会大幅降低。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询