← 返回文章

Spring IoC

发布于 2026/10/21

1. 完整回答

Spring IoC,也就是控制反转(Inversion of Control),是 Spring 框架的核心机制,主要解决对象创建、依赖管理和组件之间耦合度过高的问题。

传统开发中,一个对象通常需要主动创建它所依赖的其他对象,比如 Service 直接通过 new 创建 DAO。这样业务代码就会和具体实现绑定,后续替换实现、扩展功能或者单元测试都会比较麻烦。

引入 IoC 后,对象的创建和依赖关系交给 Spring 容器统一管理。实现控制反转的主要方式就是依赖注入(DI),由容器把对象所需的依赖提供给它。

Spring IoC 的核心实现是 BeanFactory 和 ApplicationContext。容器启动时,会读取注解或者配置文件,将 Bean 的类型、作用域、依赖关系等信息注册为 BeanDefinition。创建 Bean 时,容器根据这些定义完成对象实例化、依赖注入、初始化以及必要的后置处理,最终提供可使用的 Bean。

Spring 默认采用单例作用域,同时支持原型、请求、会话等作用域,还可以通过 BeanPostProcessor 扩展 Bean 的创建过程,例如生成 AOP 代理对象。

实际开发中,Controller、Service、Repository 等组件通常都交给 Spring 管理,通过构造器注入建立依赖关系。这样可以降低组件耦合度,提高代码的可维护性、可扩展性和可测试性。

2. 核心原理与关键概念

2.1 核心原理

Spring IoC 的核心是:利用 BeanDefinition 描述对象,通过容器统一创建和管理 Bean,再通过依赖注入建立对象之间的关系。

整体流程:

读取配置 → 注册 BeanDefinition → 执行容器扩展 → 创建 Bean → 注入依赖 → 初始化 Bean → 提供 Bean

  1. 读取配置:通过组件扫描、@Configuration、@Bean 或 XML 等方式获取 Bean 的定义信息。

  2. 注册 BeanDefinition:将 Bean 的类型、作用域、构造参数、依赖关系等信息保存到容器中,作为后续创建对象的依据。

  3. 执行容器扩展:通过 BeanFactoryPostProcessor 等机制,对 BeanDefinition 进行补充或修改。

  4. 创建 Bean:根据 BeanDefinition 选择构造方法或者工厂方法,完成对象实例化。

  5. 依赖注入:解析 Bean 所依赖的其他对象,通过构造器、Setter 或字段完成注入。

  6. 初始化 Bean:执行 BeanPostProcessor、@PostConstruct 等初始化相关逻辑,必要时生成代理对象。

  7. 提供 Bean:将完成初始化的 Bean 交给业务代码使用。单例 Bean 通常会被缓存,后续直接获取已有实例。

默认情况下,ApplicationContext 会在启动阶段预先创建非懒加载的单例 Bean;懒加载 Bean 和原型 Bean 则按各自规则创建。

2.2 关键概念

概念 核心作用
IoC 控制反转,将对象创建和依赖管理交给容器
DI 依赖注入,由容器为对象提供所需依赖
Bean 由 Spring 容器管理的对象
BeanDefinition 描述 Bean 如何创建、装配和管理的元数据
BeanFactory 提供 Bean 创建、获取和依赖管理等基础能力
ApplicationContext 在 BeanFactory 基础上增加事件、国际化及扩展机制等功能
BeanPostProcessor 在 Bean 初始化前后进行处理,支持代理对象创建等扩展

3. 高频面试追问

3.1 IoC 和 DI 有什么区别?

IoC 是设计思想,DI 是实现这种思想的主要方式。

IoC 强调控制权的转移,也就是对象的创建和依赖管理由容器负责。

DI 强调依赖关系如何建立。一个对象需要依赖其他对象时,由容器通过构造器、Setter 或字段把依赖注入进去。

例如,订单服务依赖支付服务,传统方式是订单服务主动创建支付服务;使用 DI 后,Spring 在创建订单服务时就会提供对应的支付服务实例。

所以,IoC 解决控制权归属问题,DI 解决依赖如何提供的问题。

3.2 Spring Bean 是如何注册到 IoC 容器中的?

Spring 主要通过组件扫描、配置类和 XML 等方式注册 Bean,最终统一转换为 BeanDefinition。

常见方式有三种:

  • 组件扫描:通过 @Component、@Service、@Repository、@Controller 等注解标记组件,由 Spring 扫描并注册。

  • Java 配置:通过 @Configuration 配合 @Bean,把方法返回的对象注册到容器。

  • XML 配置:通过 <bean> 标签声明 Bean。

实际项目中,自定义业务组件一般采用组件扫描,第三方客户端或者需要复杂构造逻辑的对象通常通过 @Bean 注册。

继续追问:@Component 和 @Bean 有什么区别?

@Component 标记在类上,主要通过组件扫描注册 Bean;@Bean 标记在方法上,由方法负责创建并返回 Bean。

对于能够直接修改源码的业务类,通常使用 @Component 及其派生注解;对于第三方类或需要手动控制创建过程的对象,通常使用 @Bean。

3.3 Spring Bean 的生命周期是怎样的?

Spring Bean 的生命周期主要包括实例化、属性填充、初始化、使用和销毁。

典型流程如下:

实例化 → 依赖注入 → Aware 回调 → 初始化前置处理 → 初始化方法 → 初始化后置处理 → 使用 → 销毁

具体来说:

  1. 实例化:通过构造方法或工厂方法创建对象。

  2. 依赖注入:填充属性,建立 Bean 之间的依赖关系。

  3. Aware 回调:如果实现了相关接口,可以获取 Bean 名称、BeanFactory 等容器信息。

  4. 初始化前置处理:执行 BeanPostProcessor 的 postProcessBeforeInitialization(),@PostConstruct 通常在这一阶段被调用。

  5. 初始化方法:执行 InitializingBean.afterPropertiesSet() 和自定义初始化方法。

  6. 初始化后置处理:执行 postProcessAfterInitialization(),AOP 代理对象可能在这一阶段产生。

  7. 使用和销毁:Bean 正常提供服务;容器关闭时,对符合条件的 Bean 执行 @PreDestroy 等销毁回调。

需要注意,prototype Bean 通常只由 Spring 负责创建和初始化,销毁阶段由使用方管理。

继续追问:BeanPostProcessor 有什么作用?

BeanPostProcessor 是 Spring 的扩展接口,可以在 Bean 初始化前后执行自定义逻辑。

例如,Spring AOP 可以借助 BeanPostProcessor,在初始化过程中为符合条件的 Bean 创建代理对象,从而实现事务、日志等增强功能。

3.4 Spring 有哪些依赖注入方式?为什么推荐构造器注入?

Spring 常见的依赖注入方式有构造器注入、Setter 注入和字段注入,通常优先选择构造器注入。

  • 构造器注入:创建对象时就提供依赖,适合必需的依赖。

  • Setter 注入:对象创建后再设置依赖,适合可选或者允许调整的依赖。

  • 字段注入:直接通过 @Autowired 注入字段,代码简洁,但依赖关系不够直观。

构造器注入的优势主要有三个:

第一,依赖关系明确,创建对象时就能发现缺失的必需依赖。

第二,依赖字段可以声明为 final,降低对象初始化后依赖被修改的风险。

第三,单元测试时可以直接通过构造方法传入模拟对象,减少对 Spring 容器的依赖。

另外,如果一个 Bean 只有一个构造方法,通常可以省略构造方法上的 @Autowired。

3.5 @Autowired 是如何完成自动注入的?存在多个同类型 Bean 怎么办?

@Autowired 主要根据类型寻找候选 Bean,再结合限定符、优先级等规则确定具体注入对象。

例如,一个支付接口有支付宝和微信支付两个实现,Spring 在注入支付接口时可能找到多个候选 Bean。

常见解决方式包括:

  1. 使用 @Qualifier 明确指定候选 Bean。

  2. 使用 @Primary 指定默认优先使用的实现。

  3. 在没有更高优先级匹配规则时,利用注入点名称与 Bean 名称进行匹配。

  4. 如果业务需要全部实现,可以注入 List<PaymentService> 等集合。

如果最终无法确定唯一候选对象,就会出现依赖注入异常。

实际项目中,如果多个支付实现对应不同业务场景,通常通过 @Qualifier 明确区分,避免依赖隐式匹配规则。

3.6 Spring Bean 有哪些作用域?单例 Bean 是线程安全的吗?

Spring 默认使用 singleton 单例作用域,但单例 Bean 并不天然具备线程安全性。

常见作用域包括:

作用域 含义
singleton 同一个容器中,同一个 Bean 定义通常对应一个共享实例
prototype 每次向容器请求该 Bean 时创建新实例
request 每次 HTTP 请求对应一个实例
session 每个 HTTP Session 对应一个实例

Spring 的单例主要控制对象实例的数量,线程安全则取决于对象内部是否存在被并发访问的可变状态。

例如,一个单例 Service 只调用 Repository 并使用方法局部变量处理业务,通常比较容易保证线程安全。

如果在单例 Service 中定义一个共享的计数变量,多个请求同时修改它,就可能产生并发问题。

因此,业务 Service 通常设计为无状态单例组件。

继续追问:Spring 单例和设计模式中的单例有什么区别?

Spring 单例的范围是容器和 Bean 定义,同一个类在一个容器里也可以通过不同 Bean 定义产生不同实例。

传统单例模式通常由类自身控制实例数量,常见实现是在一个类加载器范围内维护唯一实例。

3.7 BeanFactory 和 ApplicationContext 有什么区别?

BeanFactory 是 Spring IoC 容器的基础接口,ApplicationContext 在它的基础上提供更完整的应用级功能。

BeanFactory 主要负责 Bean 的创建、获取和依赖管理。

ApplicationContext 继承 BeanFactory,并提供国际化、事件发布、环境配置、自动识别和注册容器扩展组件等能力。

另外,普通 BeanFactory 实现通常按需创建 Bean,而 ApplicationContext 默认会在启动时预先实例化非懒加载的单例 Bean,有利于尽早发现配置问题。

因此,实际 Spring 和 Spring Boot 应用通常使用 ApplicationContext。

3.8 Spring 如何解决循环依赖?为什么构造器循环依赖无法正常解决?

Spring 可以在特定条件下通过提前暴露单例对象引用来处理循环依赖,但构造器之间的直接循环依赖通常无法解决。

例如,A 依赖 B,B 又依赖 A。

对于支持提前暴露引用的单例 Bean,Spring 可以通过三级缓存机制处理部分 Setter 或字段注入形成的循环依赖:

  1. 一级缓存:保存已经完成创建的单例 Bean。

  2. 二级缓存:保存提前暴露的单例对象引用。

  3. 三级缓存:保存能够生成提前引用的对象工厂。

典型过程是:

实例化 A → 暴露 A 的对象工厂 → 创建 B → B 需要 A → 获取 A 的提前引用 → 完成 B → 完成 A

三级缓存中的对象工厂还可以配合 AOP 提前生成代理引用,使循环依赖场景下的对象引用尽量保持一致。

构造器循环依赖无法通过这种方式正常解决,因为 A 创建时必须先获得 B,而 B 创建时又必须先获得 A,双方都无法完成最初的实例化。

实际开发中更推荐调整组件职责,消除循环依赖。Spring Boot 默认也禁止自动解决循环引用。

3.9 singleton Bean 注入 prototype Bean,为什么不会每次使用都创建新对象?

因为依赖注入通常发生在单例 Bean 创建阶段,prototype Bean 只会在这次注入时创建并提供给单例对象。

例如,单例订单服务依赖一个 prototype 类型的任务对象。

Spring 创建订单服务时,会创建一个任务对象并注入进去。后续每次调用订单服务,如果一直使用这个字段,访问的仍然是最初注入的任务实例。

如果希望每次执行业务都获得新的 prototype Bean,可以注入 ObjectProvider,在每次使用时通过 getObject() 获取新实例。

这类方式适用于每次任务都需要独立状态的场景。

3.10 为什么类上加了 @Autowired,运行时却没有注入成功?

常见原因是对象没有被 Spring 容器管理,或者依赖没有找到符合条件的 Bean。

例如,某个工具类通过 new 创建,即使字段上添加了 @Autowired,Spring 通常也不会自动处理这个对象的依赖注入。

另外,还可能存在组件没有被扫描、缺少 Bean 定义、多个候选 Bean 无法确定等问题。

实际排查一般按照以下顺序:

确认对象由 Spring 管理 → 检查组件扫描范围 → 检查依赖 Bean 是否注册 → 检查自动装配条件

如果是业务组件,通常直接交给 Spring 容器创建,再通过构造器注入依赖。

这样也能减少后续事务代理、生命周期回调等功能因为绕过容器而失效的问题。

4. 易混淆概念对比

4.1 IoC 与 DI

对比维度 IoC DI
含义 控制反转 依赖注入
关注点 对象创建和依赖管理由谁控制 对象依赖如何提供
核心作用 降低对象之间的耦合 建立对象之间的依赖关系
关系 设计思想 IoC 的一种主要实现方式

4.2 BeanFactory 与 ApplicationContext

对比维度 BeanFactory ApplicationContext
定位 基础容器接口 应用级容器接口
Bean 管理 支持 支持
单例初始化 具体实现通常按需创建 默认预先创建非懒加载单例
扩展能力 基础能力,需要手动配置部分扩展 自动集成后置处理器、事件、国际化等功能
使用场景 底层容器及特殊场景 常规 Spring 应用

4.3 @Component 与 @Bean

对比维度 @Component @Bean
标注位置 类 方法
注册方式 组件扫描 解析配置方法
创建控制 通常由 Spring 选择构造方式 开发者通过方法控制创建过程
使用场景 自定义业务组件 第三方组件、复杂对象配置

4.4 singleton 与 prototype

对比维度 singleton prototype
实例数量 每个容器、每个 Bean 定义共享一个实例 每次向容器请求时创建新实例
创建时机 默认启动时创建,可配置懒加载 请求获取时创建
销毁管理 容器通常负责执行销毁回调 容器通常不负责销毁
适用场景 无状态 Service、Repository 需要独立状态的任务对象

4.5 BeanFactoryPostProcessor 与 BeanPostProcessor

对比维度 BeanFactoryPostProcessor BeanPostProcessor
处理对象 BeanDefinition 等配置元数据 Bean 实例
执行时机 常规 Bean 实例化之前 Bean 初始化前后
核心作用 修改 Bean 的定义信息 扩展 Bean 的初始化过程
典型应用 解析配置、调整 BeanDefinition 处理生命周期注解、创建 AOP 代理

两者最关键的区别是:BeanFactoryPostProcessor 处理 Bean 的定义,BeanPostProcessor 处理 Bean 的实例。

5. 总结

Spring IoC 是 Spring 的核心机制,主要通过控制反转和依赖注入,将对象创建、依赖管理和生命周期管理交给容器统一完成,从而降低组件之间的耦合。它的核心流程是读取配置并注册 BeanDefinition,再根据定义完成 Bean 的实例化、依赖注入、初始化以及后置处理。Spring 默认使用单例作用域,并通过 BeanPostProcessor 提供灵活的扩展能力,例如支持 AOP 代理。实际开发中通常使用 ApplicationContext 管理业务组件,优先通过构造器注入依赖。同时需要注意,单例 Bean 的线程安全由具体实现决定,循环依赖也存在限制,因此合理划分组件职责、避免循环依赖仍然非常重要。