作为 Java 后端开发者,Spring 几乎是我们职业生涯中绕不开的框架。但很多人用了很多年 Spring,每天写着@Service、@Autowired、@Bean,却始终没有真正搞懂它的核心:
- 到底什么是控制反转?它和依赖注入是什么关系?
- Spring 是如何创建和管理 Bean 的?一个 Bean 从出生到死亡经历了什么?
- 为什么在构造方法中使用
@Autowired注入的依赖会是 null? - 如何在 Bean 初始化前后插入自定义逻辑?
这些问题不仅是日常开发中排查问题的关键,更是面试中 100% 会被深挖的核心考点。很多人能背出 Bean 生命周期的几个步骤,却不知道每个步骤的作用;能写出依赖注入的代码,却不理解背后的设计思想。
这篇文章,我会用最通俗易懂的语言,从核心思想→实现机制→底层流程→实战应用四个维度,把 Spring 的这两大核心概念讲透。看完这篇,你不仅能轻松应对所有相关面试题,更能从根本上理解 Spring 的设计哲学,写出更优雅、更健壮的代码。

一、先理清:两大核心概念的关系
在开始之前,我们先把这两个概念的关系理清楚,建立一个整体的认知框架:
- 控制反转(IoC):是 Spring 的核心设计思想,它反转了对象创建和依赖管理的控制权
- 依赖注入(DI):是控制反转的具体实现方式,Spring 通过 DI 来实现 IoC
- Bean 生命周期:是 Spring 管理 Bean 的具体执行过程,从 Bean 创建到销毁的完整流程
简单来说:IoC 是思想,DI 是实现,Bean 生命周期是执行过程。这三个概念层层递进,共同构成了 Spring IoC 容器的核心骨架。
二、控制反转(IoC):Spring 的灵魂
1. 什么是控制反转?
控制反转,全称 Inversion of Control,简称 IoC。它不是一种技术,而是一种设计思想,其核心是将对象创建和依赖管理的控制权从开发者手中转移到 Spring 容器中。
在传统的开发模式中,对象的创建和依赖管理的控制权在开发者自己手里 。如果一个对象 A 依赖对象 B,我们需要在 A 的代码中手动new B()来创建依赖对象。这种方式的问题非常明显:代码耦合度高、难以测试、代码冗余。
而在 IoC 模式中,我们不需要手动创建对象,只需要声明依赖关系,Spring 容器会自动创建对象并注入依赖。这样,对象之间的耦合度大大降低,代码也更加灵活和可测试。
2. 一个例子看懂 IoC 的优势
我们用最经典的 "用户服务依赖订单服务" 的例子来对比两种模式的区别:
传统开发模式(主动控制)
java
// 订单服务
public class OrderService {
public void createOrder() {
System.out.println("创建订单");
}
}
// 用户服务
public class UserService {
// 手动创建依赖对象,硬编码耦合
private OrderService orderService = new OrderService();
public void createUser() {
System.out.println("创建用户");
orderService.createOrder();
}
}
这种模式的致命问题:
- 耦合度极高 :如果要替换
OrderService的实现,必须修改UserService的代码 - 无法单元测试 :无法将
OrderService替换为模拟对象,测试必须依赖真实的OrderService - 代码冗余:每个需要依赖的地方都要手动创建对象
IoC 模式(被动接收)
java
// 订单服务,交给Spring容器管理
@Service
public class OrderService {
public void createOrder() {
System.out.println("创建订单");
}
}
// 用户服务,交给Spring容器管理
@Service
public class UserService {
// 声明依赖,Spring自动注入
@Autowired
private OrderService orderService;
public void createUser() {
System.out.println("创建用户");
orderService.createOrder();
}
}
在 IoC 模式中:
UserService不需要手动创建OrderService,只需要声明依赖- Spring 容器会自动创建
OrderService对象,并注入到UserService中 - 如果要替换
OrderService的实现,只需要修改OrderService的注解,不需要修改UserService的代码 - 单元测试时,可以轻松地将
OrderService替换为模拟对象
3. IoC 的核心:IoC 容器
IoC 的核心是IoC 容器。Spring 容器负责创建对象、管理对象的生命周期、注入依赖关系。它就像一个工厂,我们只需要告诉它需要什么对象,它就会为我们生产并组装好这些对象。
Spring 容器的工作流程可以概括为三步:
- 加载配置:读取 XML 配置、注解或 Java 配置,获取 Bean 的定义信息
- 创建 Bean:根据 Bean 定义信息,通过反射创建 Bean 实例
- 注入依赖:解析 Bean 之间的依赖关系,将依赖注入到对应的 Bean 中
4. BeanDefinition:Bean 的蓝图
Spring 容器是如何知道要创建哪些 Bean、如何创建 Bean 的呢?答案是BeanDefinition。
BeanDefinition 是 Bean 的 "蓝图",它包含了 Bean 的所有定义信息:
- Bean 的类名
- Bean 的作用域(单例、原型等)
- Bean 的依赖关系
- Bean 的初始化方法和销毁方法
- 其他配置信息
当 Spring 启动时,它会扫描所有的配置(@Component、@Service、@Bean等),将每个 Bean 的信息封装成一个BeanDefinition对象,存储在 IoC 容器中。然后根据这些BeanDefinition来创建和管理 Bean。
三、依赖注入(DI):IoC 的实现方式
依赖注入,全称 Dependency Injection,简称 DI。它是控制反转的具体实现方式。简单来说,依赖注入就是 Spring 容器在创建 Bean 时,自动将它依赖的其他 Bean 注入到当前 Bean 中。
1. 三种依赖注入方式详解
Spring 提供了三种依赖注入方式,分别是构造方法注入 、setter 方法注入 和字段注入。每种方式都有自己的优缺点和适用场景。
(1)构造方法注入(Spring 官方推荐)
通过构造方法注入依赖,Spring 会在实例化 Bean 时,调用构造方法并传入依赖对象。
java
@Service
public class UserService {
// 可以声明为final,保证依赖不可变
private final OrderService orderService;
// Spring 4.3以后,如果类只有一个构造方法,可以省略@Autowired注解
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
优点:
- ✅ 可以注入
final字段,保证依赖不可变 - ✅ 依赖关系明确,所有依赖都在构造方法中声明
- ✅ 避免循环依赖问题
- ✅ 单元测试时不需要启动 Spring 容器,可以手动创建依赖对象
缺点:当依赖较多时,构造方法会变得很长
(2)setter 方法注入
通过 setter 方法注入依赖,Spring 会在实例化 Bean 之后,调用 setter 方法注入依赖。
java
@Service
public class UserService {
private OrderService orderService;
@Autowired
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
}
优点:
- ✅ 支持可选依赖,可以在运行时动态修改依赖
- ✅ 不会导致循环依赖问题
缺点:
- ❌ 依赖可以被修改,可能导致不可预期的问题
- ❌ 依赖关系不明确,需要查看所有 setter 方法才能知道依赖了哪些对象
(3)字段注入(不推荐)
通过在字段上添加@Autowired注解注入依赖,这是最常用也是最简单的方式,但 Spring 官方并不推荐。
java
@Service
public class UserService {
@Autowired
private OrderService orderService;
}
优点:代码简洁,使用方便
缺点:
- ❌ 无法注入
final字段 - ❌ 容易导致循环依赖问题
- ❌ 依赖关系不明显,不利于代码可读性
- ❌ 单元测试时需要启动 Spring 容器,否则无法注入依赖
2. 三种注入方式对比与最佳实践
| 注入方式 | 代码简洁性 | 依赖不可变性 | 循环依赖 | 单元测试 | 推荐指数 |
|---|---|---|---|---|---|
| 构造方法注入 | ⭐⭐⭐ | ✅ | ❌ | ✅ | ⭐⭐⭐⭐⭐ |
| setter 方法注入 | ⭐⭐⭐ | ❌ | ❌ | ⭐⭐ | ⭐⭐⭐ |
| 字段注入 | ⭐⭐⭐⭐⭐ | ❌ | ✅(但不推荐) | ❌ | ⭐⭐ |
最佳实践:
- 优先使用构造方法注入:这是 Spring 官方推荐的方式,具有依赖不可变、依赖关系明确、便于单元测试等优点
- 对于可选依赖,使用 setter 方法注入:如果依赖是可选的,可以使用 setter 方法注入
- 尽量避免使用字段注入:虽然字段注入代码简洁,但它有很多缺点,不推荐在生产环境中使用
3. @Autowired vs @Resource
很多人搞不清这两个注解的区别,它们都是用于依赖注入的,但有以下本质不同:
| 特性 | @Autowired |
@Resource |
|---|---|---|
| 来源 | Spring 框架 | JSR-250 标准(Java 官方) |
| 注入方式 | 默认按类型注入 | 默认按名称注入 |
| 支持的注入点 | 字段、setter 方法、构造方法 | 字段、setter 方法 |
| 必须性 | 默认必须存在,否则抛出异常 | 默认必须存在,否则抛出异常 |
使用建议:
- 如果是 Spring 项目,优先使用
@Autowired - 如果需要按名称注入,可以使用
@Autowired + @Qualifier,或者直接使用@Resource
4. 常见问题:循环依赖
问题描述:两个 Bean 相互依赖,比如 A 依赖 B,B 依赖 A,导致 Spring 无法完成依赖注入。
解决方案:
- ✅ 使用构造方法注入可以避免大部分循环依赖问题
- 如果必须使用字段注入,可以使用
@Lazy注解延迟加载依赖 - 最根本的解决方案是优化代码结构,避免循环依赖
四、Bean 生命周期:Spring 如何管理 Bean
Bean 的生命周期,指的是一个 Bean 从被创建 到被销毁的整个过程。在 Spring 中,Bean 的生命周期完全由 IoC 容器管理,我们不需要手动创建和销毁对象。
1. 四个核心阶段
任何 Bean 的生命周期都可以分为四个核心阶段,按顺序执行:
- 实例化:Spring 通过反射调用 Bean 的构造方法,创建一个空的对象
- 依赖注入:Spring 解析 Bean 的依赖关系,将依赖的 Bean 注入到当前 Bean 中
- 初始化:执行 Bean 的初始化逻辑,完成自定义的初始化操作
- 销毁:当 Spring 容器关闭时,执行 Bean 的销毁方法,释放资源
2. 完整生命周期(13 步)
在四个核心阶段的基础上,Spring 提供了大量的扩展点,允许我们在 Bean 生命周期的不同时刻插入自定义逻辑。完整的生命周期分为 13 个步骤:
阶段 1:容器启动
- 加载 BeanDefinition :Spring 读取配置,将 Bean 的定义信息封装成
BeanDefinition,加载到容器中
阶段 2:Bean 实例化
- 实例化 Bean:Spring 通过反射调用 Bean 的构造方法,创建一个空的对象
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation():在实例化之前执行,可以返回一个代理对象替代原 BeanInstantiationAwareBeanPostProcessor.postProcessAfterInstantiation():在实例化之后执行,可以修改 Bean 的属性
阶段 3:依赖注入
- 依赖注入:Spring 为 Bean 注入它所依赖的其他 Bean
InstantiationAwareBeanPostProcessor.postProcessProperties():在依赖注入之后执行,可以修改 Bean 的属性值
阶段 4:初始化
- Aware 接口注入 :Spring 为实现了 Aware 接口的 Bean 注入对应的资源,比如:
BeanNameAware:注入 Bean 的名称BeanFactoryAware:注入BeanFactoryApplicationContextAware:注入ApplicationContext
BeanPostProcessor.postProcessBeforeInitialization():在初始化方法执行之前执行- 初始化方法执行 :按以下顺序执行三个初始化方法:
@PostConstruct注解标注的方法- 实现
InitializingBean接口的afterPropertiesSet()方法 - XML 或
@Bean注解指定的initMethod方法
BeanPostProcessor.postProcessAfterInitialization():在初始化方法执行之后执行,这是 AOP 代理生成的地方
阶段 5:Bean 使用
- Bean 就绪:Bean 已经完全初始化,可以被使用了
阶段 6:容器关闭
- 销毁方法执行 :按以下顺序执行三个销毁方法:
@PreDestroy注解标注的方法- 实现
DisposableBean接口的destroy()方法 - XML 或
@Bean注解指定的destroyMethod方法
3. 生命周期流程图
为了方便大家记忆,我整理了一张清晰的生命周期流程图:

4. 三个最重要的扩展点
Spring 提供了很多扩展点,但这三个是最常用也是面试最常考的:
(1)BeanPostProcessor
Bean 级别的后置处理器,在每个 Bean的初始化前后执行。
使用场景:对所有 Bean 进行统一处理,比如属性注入、代理生成、参数校验等。
代码示例:
java
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化前:" + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化后:" + beanName);
// 这里可以生成AOP代理
return bean;
}
}
(2)BeanFactoryPostProcessor
容器级别的后置处理器,在所有 BeanDefinition 加载完成之后,Bean 实例化之前执行。
使用场景 :修改BeanDefinition的属性,比如修改 Bean 的作用域、替换 Bean 的实现类等。
代码示例:
java
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");
// 修改Bean的作用域为原型
beanDefinition.setScope("prototype");
}
}
(3)InstantiationAwareBeanPostProcessor
BeanPostProcessor的子接口,在Bean 实例化前后执行。
使用场景:在实例化之前返回一个代理对象,替代原 Bean 的实例化。
5. 实战:自定义 Bean 生命周期回调
下面我们通过一个完整的例子,演示如何自定义 Bean 的生命周期回调:
java
@Service
public class UserService implements InitializingBean, DisposableBean, BeanNameAware {
private String beanName;
// 构造方法
public UserService() {
System.out.println("1. 执行构造方法,实例化Bean");
}
// BeanNameAware接口方法
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("2. 执行BeanNameAware.setBeanName(),注入Bean名称:" + name);
}
// @PostConstruct注解标注的初始化方法
@PostConstruct
public void postConstruct() {
System.out.println("3. 执行@PostConstruct标注的初始化方法");
}
// InitializingBean接口方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("4. 执行InitializingBean.afterPropertiesSet()方法");
}
// @Bean注解指定的初始化方法
public void initMethod() {
System.out.println("5. 执行@Bean指定的initMethod方法");
}
// 业务方法
public void createUser() {
System.out.println("6. 执行业务方法createUser()");
}
// @PreDestroy注解标注的销毁方法
@PreDestroy
public void preDestroy() {
System.out.println("7. 执行@PreDestroy标注的销毁方法");
}
// DisposableBean接口方法
@Override
public void destroy() throws Exception {
System.out.println("8. 执行DisposableBean.destroy()方法");
}
// @Bean注解指定的销毁方法
public void destroyMethod() {
System.out.println("9. 执行@Bean指定的destroyMethod方法");
}
}
启动 Spring 容器,运行结果如下:
1. 执行构造方法,实例化Bean
2. 执行BeanNameAware.setBeanName(),注入Bean名称:userService
3. 执行@PostConstruct标注的初始化方法
4. 执行InitializingBean.afterPropertiesSet()方法
5. 执行@Bean指定的initMethod方法
6. 执行业务方法createUser()
7. 执行@PreDestroy标注的销毁方法
8. 执行DisposableBean.destroy()方法
9. 执行@Bean指定的destroyMethod方法
可以看到,生命周期的执行顺序和我们之前讲的完全一致。
可以看到,生命周期的执行顺序和我们之前讲的完全一致。
五、常见坑与避坑指南
1. IoC/DI 常见坑
坑 1:在构造方法中使用 @Autowired 注入的依赖
问题 :在构造方法中使用@Autowired注入的依赖会是 null,因为构造方法执行时,依赖注入还没有完成。
错误示例:
java
@Service
public class UserService {
@Autowired
private OrderService orderService;
public UserService() {
// 这里orderService是null,会抛出空指针异常
orderService.createOrder();
}
}
解决方案 :使用构造方法注入,或者在@PostConstruct方法中使用依赖。
正确示例:
java
@Service
public class UserService {
private final OrderService orderService;
// 构造方法注入
public UserService(OrderService orderService) {
this.orderService = orderService;
// 这里可以安全地使用orderService
orderService.createOrder();
}
}
坑 2:字段注入导致的空指针异常
问题:在某些情况下(比如手动创建对象),字段注入的依赖会是 null,导致空指针异常。
解决方案:优先使用构造方法注入,避免字段注入。
2. Bean 生命周期常见坑
坑 1:初始化方法的执行顺序混乱
问题 :同时使用@PostConstruct、InitializingBean和initMethod,不清楚它们的执行顺序。
解决方案 :记住执行顺序:@PostConstruct → InitializingBean.afterPropertiesSet() → initMethod。推荐使用@PostConstruct注解,最简洁也最符合 Java 规范。
坑 2:BeanPostProcessor 中注入依赖导致循环依赖
问题 :在BeanPostProcessor中注入其他 Bean,可能会导致循环依赖问题。
解决方案 :尽量不要在BeanPostProcessor中注入其他 Bean,如果必须注入,使用@Lazy注解延迟注入。
六、高频面试题解答
-
问:什么是控制反转?什么是依赖注入?它们有什么关系? 答:控制反转是一种设计思想,它反转了对象创建和依赖管理的控制权,从开发者手中转移到了 Spring 容器中。依赖注入是控制反转的具体实现方式,Spring 通过依赖注入来实现控制反转。
-
问:依赖注入有哪几种方式?各自的优缺点是什么? 答:依赖注入有三种方式:构造方法注入、setter 方法注入和字段注入。构造方法注入是 Spring 官方推荐的方式,具有依赖不可变、依赖关系明确、便于单元测试等优点;setter 方法注入适合可选依赖;字段注入虽然代码简洁,但有很多缺点,不推荐使用。
-
问:Spring Bean 的完整生命周期是什么? 答:Spring Bean 的生命周期分为四个核心阶段:实例化、依赖注入、初始化、销毁。完整流程包括 13 个步骤:加载 BeanDefinition、实例化 Bean、依赖注入、Aware 接口注入、BeanPostProcessor 前置处理、初始化方法执行、BeanPostProcessor 后置处理、Bean 就绪、容器关闭、销毁方法执行。
-
问:BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别? 答:BeanPostProcessor 是 Bean 级别的后置处理器,在每个 Bean 的初始化前后执行;BeanFactoryPostProcessor 是容器级别的后置处理器,在所有 BeanDefinition 加载完成之后,Bean 实例化之前执行。
-
问:AOP 代理是在 Bean 生命周期的哪个阶段生成的? 答:AOP 代理是在
BeanPostProcessor.postProcessAfterInitialization()方法中生成的,也就是在初始化方法执行之后。 -
问:@PostConstruct、InitializingBean 和 initMethod 的执行顺序是什么? 答:执行顺序是:
@PostConstruct注解标注的方法 →InitializingBean.afterPropertiesSet()方法 → XML 或@Bean注解指定的initMethod方法。
七、总结
Spring 的核心设计思想是控制反转,依赖注入是它的实现方式,Bean 生命周期是它管理 Bean 的具体过程。这三个概念层层递进,共同构成了 Spring IoC 容器的核心骨架。
回顾一下全文的核心内容:
- 控制反转反转了对象创建和依赖管理的控制权,降低了代码耦合度
- 依赖注入有三种方式,优先使用构造方法注入
- Bean 的生命周期分为四个核心阶段,每个阶段都有对应的扩展点
- 三个最重要的扩展点:BeanPostProcessor、BeanFactoryPostProcessor、InstantiationAwareBeanPostProcessor
理解了这些核心概念,你就不再是只会用 Spring 的 "API 调用者",而是真正理解了 Spring 的设计哲学,能够从根本上解决开发中遇到的各种问题。