Spring IoC 与 DI 核心概念与原理笔记

一、 核心概念

  1. IoC (控制反转)
  • 定义:一种软件设计原则。将传统上由程序代码直接操控的对象创建、依赖管理和生命周期控制权,**转移(反转)**给外部的容器(Spring)来负责。
  • 核心目的 :实现组件之间的解耦,遵循"好莱坞原则"(Don't call us, we'll call you)。
  1. DI (依赖注入)
  • 定义 :IoC 设计原则最主流的具体实现方式。在程序运行时,框架动态地将依赖对象注入到目标对象的字段、构造器或 Setter 方法中。
  • 核心目的 :让调用方只依赖抽象(接口),而不依赖具体实现,提升代码的可扩展性可测试性
  1. 反射 (Reflection)
  • 定义 :Java 程序在运行时动态查看类的内部结构(自省)并操作类的能力。
  • 核心作用 :Spring 实现 IoC 和 DI 的底层技术基石。Spring 依靠反射在运行时动态创建对象实例、调用任意方法、访问或修改私有字段。

二、 底层实现原理(核心流程)

Spring IoC 容器的本质可以概括为:一个巨大的 Map + 疯狂的反射调用

  1. 扫描与注册(拿图纸)
  • Spring Boot 启动时,利用反射(或 ASM 字节码技术)扫描项目下所有的 .class 文件。
  • 识别带有 @Component@Service 等注解的类,将其元数据封装成 BeanDefinition(生产图纸)
  • BeanDefinition 存入底层的 ConcurrentHashMapbeanDefinitionMap)中。
  1. 实例化(造空壳)
  • Spring 遍历 Map,通过反射调用类的构造器(Constructor.newInstance())。
  • 在 JVM 堆内存中创建出属性全为默认值(null)的空对象(Bean 实例)
  • 将空对象提前放入单例缓存池(singletonObjects,即一级缓存)。
  1. 依赖注入(填属性)
  • 对象创建好后,Spring 再次通过反射检查这些空对象。
  • 发现字段或构造器上贴有 @Autowired 等注入注解。
  • 从容器缓存池中找到对应的依赖对象,通过 Field.set() 或构造器传参的方式,将依赖赋值填充进去。

核心原理面试题自测

题目 1:请简述 Spring IoC 容器创建 Bean 的核心流程是什么?

  • 参考解析:主要分为三步。
  • 第一步是扫描与注册 ,Spring 启动时扫描 .class 文件,将带有注解的类封装成 BeanDefinition 存入 Map;
  • 第二步是实例化,通过反射调用构造器创建出属性为空的原始对象,并放入单例池;
  • 第三步是依赖注入(属性填充) ,通过反射识别 @Autowired 等注解,从容器中找到对应的依赖并赋值给对象的字段。

题目 2:为什么不能在 Bean 的无参构造方法中直接调用 @Autowired 注入的属性?

  • 参考解析 :因为 Spring 创建 Bean 遵循"先实例化,后属性填充"的顺序。当无参构造方法被执行时,对象刚刚被 new 出来(实例化阶段),此时依赖注入(属性填充阶段)还没有开始,@Autowired 修饰的字段值仍然是 null。如果在此时调用,必然会触发空指针异常(NullPointerException)。

理解"先造空壳,再填属性"这个顺序,能帮你完美避开新手最常犯的一个错误:在构造方法里直接使用 @Autowired 的变量

比如,你写了这样的代码:

复制代码
@Component
public class StudentController {

    @Autowired
    private StudentService studentService; // 此时还没被 Spring 注入,值是 null

    // 构造方法
    public StudentController() {
        // ❌ 报错!空指针异常 (NullPointerException)
        // 因为 Spring 调用构造方法时,对象刚被 new 出来(第一步),
        // 此时 studentService 还是 null,属性填充(第二步)还没开始!
        studentService.doSomething(); 
    }
}

正确的做法 是,如果你非要在创建对象时就用这个依赖,就要使用构造器注入

复制代码
@Component
public class StudentController {

    private final StudentService studentService;

    // ✅ 正确!Spring 会通过这个构造方法,把找到的 studentService 传进来
    @Autowired
    public StudentController(StudentService studentService) {
        this.studentService = studentService; // 此时已经拿到了真实的对象
        studentService.doSomething(); // 可以正常调用
    }
}

(注:如果使用 Lombok 的 @RequiredArgsConstructor,它其实就是帮你自动生成了上面这个带参的构造方法。)

题目 3:Spring 框架为什么要扫描 .class 文件而不是 .java 源文件?

  • 参考解析:主要有三个原因。
  • 第一,JVM 只能识别和运行编译后的 .class 字节码,扫描 .class 符合 JVM 规范且具备跨语言(如 Kotlin, Scala)兼容性;
  • 第二,生产环境部署的 JAR 包中只包含 .class 文件,不包含源码;
  • 第三,通过 ASM 等技术直接读取 .class 字节码结构(如注解表)效率极高,且能避免直接加载类触发静态代码块等副作用。

题目 4:Spring 中的 IoC 和 DI 是什么关系?底层依靠什么技术实现?

  • 参考解析 :IoC(控制反转)是一种设计思想,目的是将对象的控制权交给容器以实现解耦;DI(依赖注入)是 IoC 思想的具体实现方式。它们的底层核心依靠的是 Java 反射机制 。Spring 利用反射在运行时动态地创建对象(Constructor.newInstance)并强制赋值私有属性(Field.set),从而实现了自动化的对象管理与装配。

为啥是spring是容器 它不就是一个框架吗?

其实,"框架"和"容器"并不是对立的,它们只是从不同维度对 Spring 的描述。

简单来说:Spring 是一个包含了强大容器功能的框架。

我们可以从以下两个角度来彻底理解为什么 Spring 既是框架又是容器:

为什么叫它"框架" (Framework)?------ 宏观的"骨架"

框架是一个宏观的概念,指的是一整套解决问题的基础设施和开发规范

Spring 提供了开发企业级应用所需的各种模块(比如 Web 开发的 Spring MVC、数据访问的 Spring Data、安全控制的 Spring Security 等)。它为你搭好了一个完整的"骨架",你只需要在这个骨架里填充自己的业务逻辑(也就是常说的"在框架里写代码")。

为什么叫它"容器" (Container)?------ 微观的"管家"

容器是一个微观的、具体的运行时环境 。之所以叫 Spring 为容器,是因为它在框架内部提供了一个核心的IoC 容器(也叫 Spring 容器)。

这个容器就像一个"超级大管家"或"对象工厂",它的核心职责是:

  1. 容纳对象 :把应用程序中成千上万个对象(在 Spring 中被称为 Bean)全部装进自己的内部(底层其实就是一个巨大的 ConcurrentHashMap)。
  2. 管理生命周期:负责这些对象的创建(实例化)、初始化(依赖注入)、使用和销毁。
  3. 协调依赖关系 :当对象 A 需要用到对象 B 时,容器会自动把 B 找出来塞给 A,而不需要 A 自己去 new

在 Spring 框架中,这个容器的具体实现就是 BeanFactory 和它的增强版 ApplicationContext。当你启动 Spring Boot 项目时,首先就是启动了这个 ApplicationContext 容器,它开始扫描、创建并管理你所有的 Bean。

一个通俗的类比

如果把开发一个 Java 应用比作经营一家大型餐厅

  • Spring 框架 :就像是整套现代化的餐厅管理体系。它不仅包含了厨房标准、服务流程、财务制度,还帮你把水、电、煤气(各种底层技术)都接通好了。
  • Spring 容器 :就像是这家餐厅里的中央厨房兼智能配菜师。所有的食材(对象/Bean)都由它统一采购、清洗、切配好,并且根据厨师(业务代码)的订单,自动把需要的食材送到案板上。

总结一下:

  • 说它是框架,是因为它提供了完整的开发体系和各种功能模块。
  • 说它是容器,是因为它的核心(IoC)通过一个运行时环境,把对象的生杀大权全部"装"起来统一管理了。

所以,平时我们说"Spring 是一个轻量级的 IoC 和 AOP 容器框架",这句话其实已经把它的两个身份都概括进去了。

如果说 IoC 是为了解决对象与对象之间的耦合(纵向关系),那么 AOP 就是为了解决业务逻辑与通用功能的耦合(横向关系)。

相关推荐
wuminyu5 小时前
专家视角看Java多态性的底层基石vtable(虚函数表)构建过程解析
java·linux·c语言·jvm·c++
phltxy5 小时前
Spring Cloud 服务注册与发现:Eureka 从原理到实战
java·spring cloud·eureka
用户860821135655 小时前
ClassLoader体系概述
spring
测试那点事儿5 小时前
零基础API 接口自动化框架源代码:结构、功能与运行时序
java·servlet·自动化
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题】【Java基础篇】第23题:ConcurrentHashMap的底层原理是什么
java·开发语言·算法·哈希算法·散列表·hash
爱怪笑的小杰杰5 小时前
优化 UniApp 日历组件的多语言切换:告别 setLocale 引起的 App 重启
java·前端·uni-app
solicitous5 小时前
JAVA系统复习(基础语法-类、接口)
java·开发语言
码途漫谈5 小时前
Easy-Vibe开发篇阅读笔记(十三)——附录之用 Dify 搭建知识库问答系统
笔记·ai·开源·ai编程
十安_数学好题速析5 小时前
【多选】成比之道:巧解三角形中比例综合
笔记·学习·高考