Spring 核心原理:IoC/DI 与 Bean 生命周期全景解析

作为 Java 后端开发者,Spring 几乎是我们职业生涯中绕不开的框架。但很多人用了很多年 Spring,每天写着@Service@Autowired@Bean,却始终没有真正搞懂它的核心:

  • 到底什么是控制反转?它和依赖注入是什么关系?
  • Spring 是如何创建和管理 Bean 的?一个 Bean 从出生到死亡经历了什么?
  • 为什么在构造方法中使用@Autowired注入的依赖会是 null?
  • 如何在 Bean 初始化前后插入自定义逻辑?

这些问题不仅是日常开发中排查问题的关键,更是面试中 100% 会被深挖的核心考点。很多人能背出 Bean 生命周期的几个步骤,却不知道每个步骤的作用;能写出依赖注入的代码,却不理解背后的设计思想。

这篇文章,我会用最通俗易懂的语言,从核心思想→实现机制→底层流程→实战应用四个维度,把 Spring 的这两大核心概念讲透。看完这篇,你不仅能轻松应对所有相关面试题,更能从根本上理解 Spring 的设计哲学,写出更优雅、更健壮的代码。

一、先理清:两大核心概念的关系

在开始之前,我们先把这两个概念的关系理清楚,建立一个整体的认知框架:

  1. 控制反转(IoC):是 Spring 的核心设计思想,它反转了对象创建和依赖管理的控制权
  2. 依赖注入(DI):是控制反转的具体实现方式,Spring 通过 DI 来实现 IoC
  3. 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 容器的工作流程可以概括为三步:

  1. 加载配置:读取 XML 配置、注解或 Java 配置,获取 Bean 的定义信息
  2. 创建 Bean:根据 Bean 定义信息,通过反射创建 Bean 实例
  3. 注入依赖:解析 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 的生命周期都可以分为四个核心阶段,按顺序执行:

  1. 实例化:Spring 通过反射调用 Bean 的构造方法,创建一个空的对象
  2. 依赖注入:Spring 解析 Bean 的依赖关系,将依赖的 Bean 注入到当前 Bean 中
  3. 初始化:执行 Bean 的初始化逻辑,完成自定义的初始化操作
  4. 销毁:当 Spring 容器关闭时,执行 Bean 的销毁方法,释放资源

2. 完整生命周期(13 步)

在四个核心阶段的基础上,Spring 提供了大量的扩展点,允许我们在 Bean 生命周期的不同时刻插入自定义逻辑。完整的生命周期分为 13 个步骤:

阶段 1:容器启动
  1. 加载 BeanDefinition :Spring 读取配置,将 Bean 的定义信息封装成BeanDefinition,加载到容器中
阶段 2:Bean 实例化
  1. 实例化 Bean:Spring 通过反射调用 Bean 的构造方法,创建一个空的对象
  2. InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation():在实例化之前执行,可以返回一个代理对象替代原 Bean
  3. InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation():在实例化之后执行,可以修改 Bean 的属性
阶段 3:依赖注入
  1. 依赖注入:Spring 为 Bean 注入它所依赖的其他 Bean
  2. InstantiationAwareBeanPostProcessor.postProcessProperties():在依赖注入之后执行,可以修改 Bean 的属性值
阶段 4:初始化
  1. Aware 接口注入 :Spring 为实现了 Aware 接口的 Bean 注入对应的资源,比如:
    • BeanNameAware:注入 Bean 的名称
    • BeanFactoryAware:注入BeanFactory
    • ApplicationContextAware:注入ApplicationContext
  2. BeanPostProcessor.postProcessBeforeInitialization():在初始化方法执行之前执行
  3. 初始化方法执行 :按以下顺序执行三个初始化方法:
    • @PostConstruct注解标注的方法
    • 实现InitializingBean接口的afterPropertiesSet()方法
    • XML 或@Bean注解指定的initMethod方法
  4. BeanPostProcessor.postProcessAfterInitialization() :在初始化方法执行之后执行,这是 AOP 代理生成的地方
阶段 5:Bean 使用
  1. Bean 就绪:Bean 已经完全初始化,可以被使用了
阶段 6:容器关闭
  1. 销毁方法执行 :按以下顺序执行三个销毁方法:
    • @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:初始化方法的执行顺序混乱

问题 :同时使用@PostConstructInitializingBeaninitMethod,不清楚它们的执行顺序。

解决方案 :记住执行顺序:@PostConstructInitializingBean.afterPropertiesSet()initMethod。推荐使用@PostConstruct注解,最简洁也最符合 Java 规范。

坑 2:BeanPostProcessor 中注入依赖导致循环依赖

问题 :在BeanPostProcessor中注入其他 Bean,可能会导致循环依赖问题。

解决方案 :尽量不要在BeanPostProcessor中注入其他 Bean,如果必须注入,使用@Lazy注解延迟注入。

六、高频面试题解答

  1. 问:什么是控制反转?什么是依赖注入?它们有什么关系? 答:控制反转是一种设计思想,它反转了对象创建和依赖管理的控制权,从开发者手中转移到了 Spring 容器中。依赖注入是控制反转的具体实现方式,Spring 通过依赖注入来实现控制反转。

  2. 问:依赖注入有哪几种方式?各自的优缺点是什么? 答:依赖注入有三种方式:构造方法注入、setter 方法注入和字段注入。构造方法注入是 Spring 官方推荐的方式,具有依赖不可变、依赖关系明确、便于单元测试等优点;setter 方法注入适合可选依赖;字段注入虽然代码简洁,但有很多缺点,不推荐使用。

  3. 问:Spring Bean 的完整生命周期是什么? 答:Spring Bean 的生命周期分为四个核心阶段:实例化、依赖注入、初始化、销毁。完整流程包括 13 个步骤:加载 BeanDefinition、实例化 Bean、依赖注入、Aware 接口注入、BeanPostProcessor 前置处理、初始化方法执行、BeanPostProcessor 后置处理、Bean 就绪、容器关闭、销毁方法执行。

  4. 问:BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别? 答:BeanPostProcessor 是 Bean 级别的后置处理器,在每个 Bean 的初始化前后执行;BeanFactoryPostProcessor 是容器级别的后置处理器,在所有 BeanDefinition 加载完成之后,Bean 实例化之前执行。

  5. 问:AOP 代理是在 Bean 生命周期的哪个阶段生成的? 答:AOP 代理是在BeanPostProcessor.postProcessAfterInitialization()方法中生成的,也就是在初始化方法执行之后。

  6. 问:@PostConstruct、InitializingBean 和 initMethod 的执行顺序是什么? 答:执行顺序是:@PostConstruct注解标注的方法 → InitializingBean.afterPropertiesSet()方法 → XML 或@Bean注解指定的initMethod方法。

七、总结

Spring 的核心设计思想是控制反转,依赖注入是它的实现方式,Bean 生命周期是它管理 Bean 的具体过程。这三个概念层层递进,共同构成了 Spring IoC 容器的核心骨架。

回顾一下全文的核心内容:

  • 控制反转反转了对象创建和依赖管理的控制权,降低了代码耦合度
  • 依赖注入有三种方式,优先使用构造方法注入
  • Bean 的生命周期分为四个核心阶段,每个阶段都有对应的扩展点
  • 三个最重要的扩展点:BeanPostProcessor、BeanFactoryPostProcessor、InstantiationAwareBeanPostProcessor

理解了这些核心概念,你就不再是只会用 Spring 的 "API 调用者",而是真正理解了 Spring 的设计哲学,能够从根本上解决开发中遇到的各种问题。

相关推荐
weixin_4896900213 小时前
NAS部署实测:Solon vs Spring Boot,从内存到包体积的“降维打击”
java·spring boot·后端
枕星而眠13 小时前
数据结构哈希表(散列表)超详细总结
c语言·数据结构·后端·散列表
tongluowan00713 小时前
怎么保证缓存和数据库的一致性
java·数据库·缓存·一致性
一条泥憨鱼13 小时前
【Java 进阶】LinkedHashMap 与 TreeMap
java·开发语言·数据结构·笔记·后端·学习
ゆづき13 小时前
假如编程语言们有外号
java·c语言·c++·python·学习·c#·生活
凤山老林13 小时前
63-Java LinkedList(链表)
java·开发语言·链表
Lee川13 小时前
MCP(Model Context Protocol)深度解析:从面试概念到代码实现
人工智能·面试
TDengine (老段)13 小时前
TDengine 支持数据类型深度解析 — 类型体系、存储编码与选型指南
java·大数据·数据库·系统架构·时序数据库·tdengine·涛思数据
浮尘笔记15 小时前
Java Snowy框架CI/CD云效自动化部署流程
java·运维·服务器·阿里云·ci/cd·自动化