一、 核心概念
- IoC (控制反转)
- 定义:一种软件设计原则。将传统上由程序代码直接操控的对象创建、依赖管理和生命周期控制权,**转移(反转)**给外部的容器(Spring)来负责。
- 核心目的 :实现组件之间的解耦,遵循"好莱坞原则"(Don't call us, we'll call you)。
- DI (依赖注入)
- 定义 :IoC 设计原则最主流的具体实现方式。在程序运行时,框架动态地将依赖对象注入到目标对象的字段、构造器或 Setter 方法中。
- 核心目的 :让调用方只依赖抽象(接口),而不依赖具体实现,提升代码的可扩展性 与可测试性。
- 反射 (Reflection)
- 定义 :Java 程序在运行时动态查看类的内部结构(自省)并操作类的能力。
- 核心作用 :Spring 实现 IoC 和 DI 的底层技术基石。Spring 依靠反射在运行时动态创建对象实例、调用任意方法、访问或修改私有字段。
二、 底层实现原理(核心流程)
Spring IoC 容器的本质可以概括为:一个巨大的 Map + 疯狂的反射调用。
- 扫描与注册(拿图纸)
- Spring Boot 启动时,利用反射(或 ASM 字节码技术)扫描项目下所有的
.class文件。 - 识别带有
@Component、@Service等注解的类,将其元数据封装成BeanDefinition(生产图纸)。 - 将
BeanDefinition存入底层的ConcurrentHashMap(beanDefinitionMap)中。
- 实例化(造空壳)
- Spring 遍历 Map,通过反射调用类的构造器(
Constructor.newInstance())。 - 在 JVM 堆内存中创建出属性全为默认值(null)的空对象(Bean 实例)。
- 将空对象提前放入单例缓存池(
singletonObjects,即一级缓存)。
- 依赖注入(填属性)
- 对象创建好后,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 容器)。
这个容器就像一个"超级大管家"或"对象工厂",它的核心职责是:
- 容纳对象 :把应用程序中成千上万个对象(在 Spring 中被称为 Bean)全部装进自己的内部(底层其实就是一个巨大的
ConcurrentHashMap)。 - 管理生命周期:负责这些对象的创建(实例化)、初始化(依赖注入)、使用和销毁。
- 协调依赖关系 :当对象 A 需要用到对象 B 时,容器会自动把 B 找出来塞给 A,而不需要 A 自己去
new。
在 Spring 框架中,这个容器的具体实现就是 BeanFactory 和它的增强版 ApplicationContext。当你启动 Spring Boot 项目时,首先就是启动了这个 ApplicationContext 容器,它开始扫描、创建并管理你所有的 Bean。
一个通俗的类比
如果把开发一个 Java 应用比作经营一家大型餐厅:
- Spring 框架 :就像是整套现代化的餐厅管理体系。它不仅包含了厨房标准、服务流程、财务制度,还帮你把水、电、煤气(各种底层技术)都接通好了。
- Spring 容器 :就像是这家餐厅里的中央厨房兼智能配菜师。所有的食材(对象/Bean)都由它统一采购、清洗、切配好,并且根据厨师(业务代码)的订单,自动把需要的食材送到案板上。
总结一下:
- 说它是框架,是因为它提供了完整的开发体系和各种功能模块。
- 说它是容器,是因为它的核心(IoC)通过一个运行时环境,把对象的生杀大权全部"装"起来统一管理了。
所以,平时我们说"Spring 是一个轻量级的 IoC 和 AOP 容器框架",这句话其实已经把它的两个身份都概括进去了。
如果说 IoC 是为了解决对象与对象之间的耦合(纵向关系),那么 AOP 就是为了解决业务逻辑与通用功能的耦合(横向关系)。