📚 目录
- 🚀 Spring IoC 与 DI 深度剖析:从"控制反转"到 Bean 的集中管理
1. 什么是 Spring?IoC 与 DI 概览
Spring 框架是一个轻量级、一站式、模块化 的开源框架,旨在简化企业级应用程序开发。它是一个包含众多工具方法的 IoC 容器,支持广泛的应用场景,并具有活跃而庞大的社区。
Spring 的主要功能包括:管理对象 及其之间的依赖关系 、面向切面编程 (AOP) 、数据库事务管理 、数据访问 以及Web 框架支持等。
1.1 Spring、Spring MVC 与 Spring Boot 的关系
为了更好地理解 Spring 生态系统,我们首先要明确三者之间的关系:
| 框架名称 | 定位/核心功能 | 与其他框架关系 |
|---|---|---|
| Spring | 核心框架,提供 IoC 和 AOP 等基础功能。 | 基础和核心,其他两个框架都建立在 Spring 之上。 |
| Spring MVC | Spring 的子框架,用于构建 Web 应用和网络接口。 | 基于 Spring 开发的 Web 框架,提供 URL 映射和视图集成等功能。 |
| Spring Boot | 一套快速开发整合包,是对 Spring 的封装和简化。 | 辅助简化项目开发,通过"约定大于配置"快速搭建 Spring 应用,例如通过引入 Spring MVC 框架来完成 Web 开发。 |
简单来说,Spring 是地基 ,Spring MVC 是 Web 层的工具 ,而 Spring Boot 是帮你快速搭建和整合项目的一把"脚手架"。
1.2 容器(Container)的概念
在技术领域,容器本质上是用来容纳某种物品的基本装置。在编程中,我们常见的容器包括:
- 数据存储容器 :如
List或Map。 - Web 容器:如 Tomcat,用于运行 Web 应用。
- IoC 容器:即 Spring 容器,用于管理应用中的对象(Bean)。
1.3 控制反转(IoC):核心思想的转变

IoC 是 Inversion of Control 的缩写,中文即控制反转。它是 Spring 的核心思想。
在传统的开发模式中,当一个对象需要依赖另一个对象时,开发者通常需要手动通过 new 关键字来创建 这个依赖对象。而在 IoC 模式下,获得依赖对象的过程被反转了 。开发者不再需要自己创建对象,而是将创建对象的任务交给 IoC 容器来完成。
💡 核心: 对象的创建和管理权从应用程序代码(使用方)反转到了 IoC 容器(第三方)。
1.4 依赖注入(DI):IoC 的具体实现
DI 是 Dependency Injection 的缩写,中文即依赖注入。
**依赖注入(DI)**是指容器在程序运行期间,动态地为应用程序提供其运行时所依赖的资源(即对象)的过程。
- IoC 是一种思想和目标。
- DI 是 IoC 的一种实现方式和落地方案。
从应用程序的角度来看,通过引入 IoC 容器,并利用依赖关系注入的方式,最终实现了对象之间的解耦。
2. IoC 思想:从高耦合到低耦合的演进
通过一个"造车"的案例,我们可以直观地理解 IoC 解决的耦合度过高的问题。
2.1 传统程序开发:高耦合的困境
在传统的程序设计中,依赖关系是自上而下的:汽车(Car)依赖车身(Framework),车身依赖底盘(Bottom),底盘依赖轮胎(Tire)。

在代码实现中,上层类会主动创建并控制下层依赖对象。例如,Car 类在其构造函数中会主动 new Framework(),Framework 会 new Bottom(),以此类推:
java
// 传统模式下,Car内部主动创建依赖对象Framework
static class Car {
private Framework framework;
public Car() {
framework = new Framework(); // ⚠️ 耦合点1:Car主动创建Framework
System.out.println("Car init....");
}
// ...
}
// Framework 内部主动创建依赖对象Bottom
static class Framework {
private Bottom bottom;
public Framework() {
bottom = new Bottom(); // ⚠️ 耦合点2:Framework主动创建Bottom
System.out.println("Framework init...");
}
// ...
}
// ...以此类推,当Tire类构造函数发生变更时...
问题分析 :
如果需求变动,例如需要为 Tire 类添加一个尺寸参数,那么 Tire 构造函数的变化会沿着整个调用链向上影响到 Bottom、Framework,最终导致 Car 类的代码也必须修改。这导致程序的耦合度非常高。
2.2 IoC 改造:实现解耦与控制权反转
为了解决高耦合问题,我们采用依赖注入的方式,将原来由类自身创建下级类,改为通过**传递(注入)**的方式获取。
在 IoC 模式中,对象之间的依赖关系是反向的(控制权反转 ),对象的创建顺序也颠倒了:Tire → \to → Bottom → \to → Framework → \to → Car。
以下是基于构造方法注入的 IoC 改造示例代码:
java
// IoC 改造后的 Car 类:不再主动创建 Framework,而是通过构造方法接收
static class Car {
private Framework framework;
// 依赖对象(Framework)由外部注入
public Car(Framework framework) {
this.framework = framework; // ✅ 注入点
System.out.println("Car init....");
}
// ...
}
// IoC 改造后的 Framework 类
static class Framework {
private Bottom bottom;
// 依赖对象(Bottom)由外部注入
public Framework(Bottom bottom) {
this.bottom = bottom; // ✅ 注入点
System.out.println("Framework init...");
}
// ...
}
// ...
// 对象的创建和组装工作集中在"IoC容器"中完成
public class IocCarExample {
public static void main(String[] args) {
Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
可以看到,Car、Framework 等类本身不再关心如何创建其依赖对象,它们只需要通过构造函数接收即可。即使 Tire 类构造函数变化,也只需要修改最顶层的调用程序(main 方法中的组装逻辑),底层的业务类代码不受影响。
2.3 IoC 带来的优势总结
控制反转带来的优势主要体现在两个方面:
- 资源集中管理(低配置成本) :
- 资源(对象)不由使用双方管理,而是由**第三方(IoC 容器)**统一管理。
- 实现了资源的可配置和易管理,开发者可以专注于业务逻辑,无需关注实例创建的细节。
- 降低耦合度(高可维护性) :
- 降低了使用资源双方的依赖程度。
- 依赖类发生任何改变,当前使用方类都不受影响,实现了灵活、通用的程序设计。
3. Spring Bean 的集中存储(IoC)
Spring IoC 容器管理的对象被称为 Bean。要将一个对象交给 Spring 容器管理,我们需要使用特定的注解来声明它。
Spring 提供了两类注解用于 Bean 的存储:
- 类注解 :
@Controller、@Service、@Repository、@Component、@Configuration。 - 方法注解 :
@Bean。
3.1 类注解:五大"组件存储"注解

Spring 提供了五种用于将类标记为 Bean 的注解,它们都属于 @Component 的衍生注解 。使用不同的注解是为了实现应用分层,让开发者看到注解就能直观了解类的用途。
| 注解名称 | 对应应用层 | 职责描述 | 示例类名 |
|---|---|---|---|
@Controller |
控制层 (Web/API) | 接收请求、处理请求并进行响应。 | UserController |
@Service |
业务逻辑层 | 处理具体的业务逻辑,编排和调用 Dao/Repository 层。 | UserService |
@Repository |
数据访问层 (持久层) | 负责数据访问操作,与数据库交互。 | UserRepository |
@Configuration |
配置层 | 处理项目中的配置信息,通常与 @Bean 配合使用。 |
AppConfig |
@Component |
通用组件 | 泛指任何不属于上述层级的通用组件或工具类。 | ToolComponent |
3.1.1 @Controller:控制层
用于标注处理 HTTP 请求的控制器类。
java
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi() {
System.out.println("hi, UserController...");
}
}
3.1.2 @Service:业务逻辑层
用于标注业务逻辑处理类。
java
@Service
public class UserService {
public void sayHi (String name) {
System.out.println("Hi," + name);
}
}
3.1.3 @Repository:数据访问层/持久层
用于标注数据访问或持久化操作的类。
java
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository~");
}
}
3.1.4 @Configuration:配置层
用于标注配置类,通常包含 @Bean 方法用于创建 Bean。
java
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi, UserConfiguration~");
}
}
3.1.5 @Component:通用组件
一个通用注解,是所有组件存储注解的"父类"。
java
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent~");
}
}
3.2 类注解的本质:@Component 的衍生
查看 @Controller、@Service、@Repository 等注解的源码,我们会发现它们内部都包含了 @Component 注解。
因此,@Component 被称为元注解 ,而 @Controller、@Service、@Repository 等是它的衍生注解 。它们在功能上等价于 @Component,但提供了更明确的语义,便于应用分层和工具处理。在实际开发中,我们应尽量使用语义更明确的衍生注解,如在业务逻辑层使用 @Service,而非 @Component。
3.3 方法注解:@Bean 的灵活应用
@Bean 注解是添加到方法上的,用于将方法的返回值对象注册为 Spring Bean。它主要用于解决以下问题:
- 外部包的类:无法修改源码,无法添加类注解。
- 一个类需要定义多个对象:例如配置多个数据源实例。
注意:
@Bean方法必须定义在一个被@Component或@Configuration(本身也包含@Component)注解标记的类中才能生效。
java
// BeanConfig 必须被组件注解标记
@Component
public class BeanConfig {
@Bean // 将方法的返回值对象注册为 Bean
public User user() {
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
3.3.1 Bean 的默认命名规则

在使用 @Bean 标记方法时,如果没有显式指定名称,Spring 会默认将方法名作为 Bean 的名称(BeanId)。
java
// Bean 名称默认为 "user1"
@Bean
public User user1() { /* ... */ }
// Bean 名称默认为 "user2"
@Bean
public User user2() { /* ... */ }
3.3.2 自定义 Bean 名称
可以通过设置 @Bean 注解的 name 属性或直接作为参数,为 Bean 对象进行重命名,并且可以指定多个别名。
java
// 使用 name 属性指定多个别名
@Bean(name = {"u1", "user1Alias"})
public User user1() { /* ... */ }
// name={} 可以省略,直接传入字符串数组
@Bean({"u2", "user2Alias"}) // Bean 名称/别名为 "u2" 和 "user2Alias"
public User user2() { /* ... */ }
// 只有一个名称时,{} 也可以省略
@Bean("u3") // Bean 名称为 "u3"
public User user3() { /* ... */ }
3.4 Bean 扫描机制与 @SpringBootApplication
使用五大类注解声明的 Bean,想要生效,必须被 Spring 扫描到。
Spring 通过 @ComponentScan 注解来配置扫描路径。
java
@ComponentScan({"com.example.demo.controller", "com.example.demo.service"})
@SpringBootApplication
// ...
在 Spring Boot 应用中,通常无需手动添加 @ComponentScan,这是因为启动类上的 @SpringBootApplication 注解已经默认包含了 @ComponentScan。
默认扫描范围 :Spring Boot 启动类 (@SpringBootApplication 所在的类) 所在包及其所有子包。
推荐做法 :将 Spring Boot 启动类 (SpringIocDemoApplication) 放置在项目根包下(例如 com.example.demo),这样它就能默认扫描到所有的子包(如 controller、service、repository 等)中定义的 Bean。

4. 依赖注入(DI)的实现方式与进阶
依赖注入是 IoC 容器在创建 Bean 时,为其提供运行时所依赖的资源(对象)的过程。Spring 提供了三种主要的依赖注入方式:
- 属性注入 (Field Injection)
- 构造方法注入 (Constructor Injection)
- Setter 注入 (Setter Injection)
所有注入方式主要通过 @Autowired 注解实现。
4.1 属性注入(Field Injection)
在类的成员属性上使用 @Autowired 注解。这种方式最简洁、方便。
java
@Controller
public class UserController {
// 注入方法1: 属性注入
@Autowired
private UserService userService; //
public void sayHi () {
System.out.println("hi, UserController...");
userService.sayHi(); // 如果没有 @Autowired,这里会抛出 NullPointerException
}
}
4.2 构造方法注入(Constructor Injection) 【Spring 推荐】
在类的构造方法上使用 @Autowired 注解。如果类中只有一个构造方法,@Autowired 可以省略。
java
@Controller
public class UserController2 {
private final UserService userService; // 推荐使用 final 修饰
@Autowired // 如果是唯一的构造方法,@Autowired 可省略
public UserController2(UserService userService) {
this.userService = userService;
}
// ...
}
4.3 Setter 注入(Setter Injection)
在类的 Setter 方法上使用 @Autowired 注解。
java
@Controller
public class UserController3 {
private UserService userService;
@Autowired // 在 Setter 方法上添加 @Autowired
public void setUserService (UserService userService) {
this.userService = userService;
}
// ...
}
4.4 三种注入方式的优缺点对比
| 注入方式 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 属性注入 | 简洁、使用方便。 | 1. 只能用于 IoC 容器,耦合度较高。 2. 无法注入 final 属性。 3. 只有在使用时才会抛出空指针异常 (NPE)。 |
不推荐 |
| 构造方法注入 | 1. 可以注入 final 修饰的属性。 2. 依赖对象在使用前一定被完全初始化。 3. 注入的对象不会被修改(天然不可变)。 4. 通用性好,JDK支持。 |
注入多个对象时,代码会比较繁琐。 | Spring 4.X/5.X 推荐 |
| Setter 注入 | 方便在类实例之后,重新对该对象进行配置或注入。 | 1. 不能注入 final 属性。 2. 注入对象可能会被修改(setter 可被多次调用)。 |
Spring 3.X 推荐 |
最佳实践: 构造方法注入是 Spring 官方推荐的依赖注入方式,因为它保证了依赖的不可变性(
final)和非空性(依赖必须在构造时传入)。
5. 解决多 Bean 冲突:@Autowired、@Qualifier 与 @Resource
当 Spring 容器中存在多个相同类型 的 Bean 时,如果仅使用 @Autowired 按类型注入,程序会因无法确定注入哪个 Bean 而报错 NoUniqueBeanDefinitionException(非唯一 Bean 对象)。
5.1 @Autowired 装配顺序
为了解决多 Bean 冲突,Spring 提供了明确的装配顺序:
- 按类型查找 Bean :
- 如果只找到一个,则自动装配。
- 如果找到多个,则进入下一步。
- 是否配置
@Qualifier或@Primary:- 如果配置了
@Qualifier,则按Qualifier参数查找 Bean。 - 如果没有配置
@Qualifier,则进入下一步。
- 如果配置了
- 按名称查找 Bean :
- 使用注入字段的名称作为 Bean 的名称进行查找。
- 最终结果 :
- 如果找到一个,则自动装配。
- 如果仍未找到或找到多个,则抛出异常。
5.2 解决方案一:@Primary 指定默认 Bean
当存在多个相同类型的 Bean 时,在其中一个 Bean 的定义上加上 @Primary 注解,可以指定它为默认实现。
java
@Component
public class BeanConfig {
@Primary // 指定该 bean 为默认 bean 的实现
@Bean("u1")
public User user1() { /* user1: zhangsan */ }
@Bean
public User user2() { /* user2: lisi */ }
}
@Controller
public class UserController {
// 此时 @Autowired 会优先注入带有 @Primary 的 user1
@Autowired
private User user;
}
5.3 解决方案二:@Qualifier 按名称匹配
@Qualifier 注解用于指定当前要注入的 Bean 的名称,它必须配合 @Autowired 一起使用。
java
@Controller
public class UserController {
@Qualifier("user2") // 明确指定注入名为 "user2" 的 Bean
@Autowired
private User user; // 将注入 user2
}
5.4 解决方案三:@Resource 按名称注入 【JDK标准】
@Resource 是 JDK 提供 的注解,默认是按照 Bean 的名称 (即 name 属性)进行注入。
java
@Controller
public class UserController {
// 通过 name 属性指定注入名为 "user2" 的 Bean
@Resource(name = "user2")
private User user; // 将注入 user2
}
5.5 面试题:@Autowired 与 @Resource 的区别
| 特性 | @Autowired |
@Resource |
|---|---|---|
| 提供方 | Spring 框架 | JDK (Java 标准) |
| 默认注入方式 | **按类型(Type)**注入 | **按名称(Name)**注入 |
| 名称查找 | 在按类型查找失败(多 Bean 冲突)后,会尝试按名称查找。 | 直接通过 name 属性或字段名查找 Bean。 |
| 配合注解 | 可配合 @Qualifier 实现按名称注入。 |
支持更多参数设置,例如 name 属性。 |
推荐使用: 在 Spring Boot/Spring Cloud 项目中,@Autowired 配合构造方法注入是首选;如果需要兼容 Java EE 或按名称明确指定依赖,可以使用 @Resource。
总结与展望
本文对 Spring IoC 和 DI 进行了全面且深入的探讨:
- IoC(控制反转)是核心思想,将对象创建和管理的控制权从应用代码反转给 IoC 容器,实现了彻底的解耦。
- **DI(依赖注入)**是实现 IoC 的具体手段,即容器在运行时为对象提供其依赖资源。
- Bean 存储 :我们使用
@Controller、@Service、@Repository、@Configuration(均是@Component的衍生) 和@Bean来将对象注册到 Spring 容器中。 - 依赖注入方式:包括属性注入、构造方法注入(推荐)和 Setter 注入。
- 多 Bean 解决 :通过
@Primary、@Qualifier(配合@Autowired)或@Resource来解决多类型 Bean 注入冲突问题。
掌握 IoC 和 DI,是迈向 Spring 高级开发的第一步。它们是 Spring 框架的基石,也是设计高内聚、低耦合、可维护系统的关键。

