1.1.1 什么是容器?
容器是用来容纳某种物品的(基本)装置。------ 来自:百度百科生活中的水杯,垃圾桶,冰箱等等这些都是容器。我们想想,之前课程我们接触的容器有哪些?
- List/Map -> 数据存储容器
- Tomcat -> Web 容器
1.1.2 什么是 IoC?
IoC 是 Spring 的核心思想,也是常见的面试题,那什么是 IoC 呢?其实 IoC 我们在前面已经使用了,我们在前面讲到,在类上面添加 @RestController 和 @Controller 注解,就是把这个对象交给 Spring 管理,Spring 框架启动时就会加载该类。把对象交给 Spring 管理,就是 IoC 思想。
IoC: Inversion of Control (控制反转),也就是说 Spring 是一个 "控制反转" 的容器。
什么是控制反转呢?也就是控制权反转。什么的控制权发生了反转?获得依赖对象的过程被反转了。也就是说,当需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入 (Dependency Injection,DI) 就可以了。这个容器称为:IoC 容器。Spring 是一个 IoC 容器,所以有时 Spring 也称为 Spring 容器。
控制反转是一种思想,在生活中也是处处体现。
- 比如自动驾驶,传统驾驶方式,车辆的横向和纵向驾驶控制权由驾驶员来控制,现在交给了驾驶自动化系统来控制,这也是控制反转思想在生活中的实现。
- 比如招聘,企业的员工招聘,入职,解雇等控制权,由老板转交给 HR (人力资源) 来处理。
1.2 IoC 介绍
接下来我们通过案例来了解一下什么是 IoC
需求:造一辆车
1.2.1 传统程序开发
我们的实现思路是这样的:
先设计轮子 (Tire),然后根据轮子的大小设计底盘 (Bottom),接着根据底盘设计车身 (Framework),最后根据车身设计好整个汽车 (Car)。这里就出现了一个 "依赖" 关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。

依赖关系链:汽车 (Car) → 依赖 → 车身 (Framework) → 依赖 → 底盘 (Bottom) → 依赖 → 轮胎 (Tire)
最终程序的实现代码如下:
java
public class NewCarExample {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
/**
* 汽车对象
*/
static class Car {
private Framework framework;
public Car() {
framework = new Framework();
System.out.println("Car init....");
}
public void run() {
System.out.println("Car run...");
}
}
/**
* 车身类
*/
static class Framework {
private Bottom bottom;
public Framework() {
bottom = new Bottom();
System.out.println("Framework init...");
}
}
/**
* 底盘类
*/
static class Bottom {
private Tire tire;
public Bottom() {
this.tire = new Tire();
System.out.println("Bottom init...");
}
}
/**
* 轮胎类
*/
static class Tire {
// 尺寸
private int size;
public Tire() {
this.size = 17;
System.out.println("轮胎尺寸: " + size);
}
}
}
1.2.2 问题分析
这样的设计看起来没问题,但是可维护性却很低。接下来需求有了变更:随着对的车的需求量越来越大,个性化需求也会越来越多,我们需要加工多种尺寸的轮胎。那这个时候就要对上面程序进行修改了,修改后的代码如下所示:

修改之后,其他调⽤程序也会报错,我们需要继续修改 完整代码如下:

从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调用链上的所有代码都需要 修改. 程序的耦合度非常高(修改⼀处代码,影响其他处的代码修改)
1.2.3 解决方案
在上面的程序中,我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改。同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改,也就是整个设计几乎都得改
我们尝试换一种思路,我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘,底盘依赖车身,车身依赖汽车
这就类似我们打造一辆完整的汽车,如果所有的配件都是自己造,那么当客户需求发生改变的时候,比如轮胎的尺寸不再是原来的尺寸了,那我们要自己动手来改了,但如果我们是把轮胎外包出去,那么即使是轮胎的尺寸发生变了,我们只需要向代理工厂下订单就行了,我们自身是不需要出力的。
如何来实现呢:
我们可以尝试不在每个类中自己创建下级类,如果自己创建下级类就会出现当下级类发生改变操作,自己也要跟着修改。此时,我们只需要将原来由自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦。
1.2.4 IoC 程序开发
基于以上思路,我们把调用汽车的程序示例改造一下,把创建子类的方式,改为注入传递的方式。具体实现代码如下:
java
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();
}
static class Car {
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
System.out.println("Car init....");
}
public void run() {
System.out.println("Car run...");
}
}
static class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("Framework init...");
}
}
static class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("Bottom init...");
}
}
static class Tire {
private int size;
public Tire(int size) {
this.size = size;
System.out.println("轮胎尺寸: " + size);
}
}
}
- 解耦核心改动 :每个类不再自行创建依赖的下级类实例,而是通过构造方法注入(传递)已创建好的实例;
- 依赖控制权转移 :对象的创建和组装逻辑从类内部转移到
main方法(外部),实现 "控制反转"; - 运行效果 :执行后输出如下,且修改轮胎尺寸 / 替换下级类实现时,仅需调整
main方法中的创建逻辑,无需修改类本身:
代码经过以上调整,⽆论底层类如何变化,整个调⽤链是不⽤做任何改变的,这样就完成了代码之间 的解耦,从⽽实现了更加灵活、通⽤的程序设计了。
原设计的耦合程度过高,修改后的方案有效降低了模块间的耦合度,同时实现了类似 "外包式模块化管理" 的效果 ------ 各组件职责边界清晰,可独立维护、灵活调整,大幅提升了系统的可扩展性与可维护性。
1.2.5 IoC 优势
在传统的代码中对象创建顺序是:Car-> Framework-> Bottom-> Tire改进之后解耦的代码的对象创建顺序是:Tire-> Bottom-> Framework-> Car

我们发现了一个规律,通用程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了 Framework,Framework 创建并创建了 Bottom,依次往下,而改进之后的控制权发生反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了。
这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。
学到这里,我们大概就知道了什么是控制反转了,那什么是控制反转容器呢,也就是 IoC 容器

从上面也可以看出来,IoC 容器具备以下优点:
资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。
- 资源集中管理: IoC 容器会帮我们管理一些资源 (对象等),我们需要使用时,只需要从 IoC 容器中去取就可以了
- 我们在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合度。
Spring 就是一种 IoC 容器,帮助我们来做了这些资源管理。
1.3 DI 介绍
上面学习了 IoC, 什么是 DI 呢?
DI: Dependency Injection (依赖注入)
容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入。
程序运行时需要某个资源,此时容器就为其提供这个资源.
从这点来看,依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,依赖注入是从应用程序的角度来描述,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦.
上述代码中,是通过构造函数的方式,把依赖对象注入到需要使用的对象中的

IoC 是一种思想,也是 "目标",而思想只是一种指导原则,最终还是要有可行的落地方案,而 DI 就属于具体的实现。所以也可以说,DI 是 IoC 的一种实现。
比如说我今天心情比较好,吃一顿好的犒劳犒劳自己,那么 "吃一顿好的" 是思想和目标(是 IoC),但最后我是吃海底捞还是杨国福?这就是具体的实现,就是 DI。
2. IoC & DI 使用
对 IoC 和 DI 有了初步的了解,我们接下来具体学习 Spring IoC 和 DI 的代码实现。依然是先使用,再学习
既然 Spring 是一个 IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能:
- 存
- 取
Spring 容器管理的主要是对象,这些对象,我们称之为 "Bean"。我们把这些对象交由 Spring 管理,由 Spring 来负责对象的创建和销毁。我们程序只需要告诉 Spring,哪些需要存,以及如何从 Spring 中取出对象
目标:把 BookDao, BookService 交给 Spring 管理,完成 Controller 层,Service 层,Dao 层的解耦步骤:
- Service 层及 Dao 层的实现类,交给 Spring 管理:使用注解:@Component
- 在 Controller 层和 Service 注入运行时依赖的对象:使用注解 @Autowired
实现:
- 把 BookDao 交给 Spring 管理,由 Spring 来管理对象
java
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
// 补充BookInfo实体类(保证代码语法完整,图片中未显示但代码依赖)
class BookInfo {
private Integer id;
private String bookName;
private String author;
private Integer count;
private BigDecimal price;
private String publish;
private Integer status;
private String statusCN;
// 基础setter方法(代码中用到的)
public void setId(Integer id) {
this.id = id;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public void setAuthor(String author) {
this.author = author;
}
public void setCount(Integer count) {
this.count = count;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public void setPublish(String publish) {
this.publish = publish;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getStatus() {
return status;
}
public void setStatusCN(String statusCN) {
this.statusCN = statusCN;
}
}
/**
* 1. 交给Spring管理的BookDao
*/
@Component
public class BookDao {
/**
* 数据Mock 获取图书信息
* @return
*/
public List<BookInfo> mockData() {
List<BookInfo> books = new ArrayList<>();
for (int i = 0; i < 5; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("书籍" + i);
book.setAuthor("作者" + i);
book.setCount(i * 5 + 3);
book.setPrice(new BigDecimal(new Random().nextInt(100)));
book.setPublish("出版社" + i);
book.setStatus(1);
books.add(book);
}
return books;
}
}
/**
* 2. 交给Spring管理的BookService(改造前,仍手动创建BookDao)
*/
@Component
public class BookService {
private BookDao bookDao = new BookDao();
public List<BookInfo> getBookList() {
List<BookInfo> books = bookDao.mockData();
for (BookInfo book : books) {
if (book.getStatus() == 1) {
book.setStatusCN("可借阅");
} else {
book.setStatusCN("不可借阅");
}
}
return books;
}
}
/**
* 3. 改造后的BookService(删除手动创建,从Spring注入)
* (图片中仅标注改造步骤,此处补充完整改造后代码)
*/
// @Component
// public class BookService {
// // 替换为@Autowired注入,不再手动new
// @Autowired
// private BookDao bookDao;
//
// public List<BookInfo> getBookList() {
// List<BookInfo> books = bookDao.mockData();
// for (BookInfo book : books) {
// if (book.getStatus() == 1) {
// book.setStatusCN("可借阅");
// } else {
// book.setStatusCN("不可借阅");
// }
// }
// return books;
// }
// }
- BookDao :通过
@Component注解被 Spring 管理,核心方法mockData()生成 5 条模拟图书数据,包含 id、书名、作者等字段; - BookService(改造前) :同样加
@Component交给 Spring,但仍通过new BookDao()手动创建依赖,未实现解耦; - 改造关键点 :第三步需删除
private BookDao bookDao = new BookDao();,替换为@Autowired private BookDao bookDao;,从 Spring 容器中注入 BookDao 对象,完成 DI 依赖注入和解耦
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
// 基础实体类(代码依赖,补充完整)
class BookInfo {
private Integer id;
private String bookName;
private String author;
private Integer count;
private BigDecimal price;
private String publish;
private Integer status;
private String statusCN;
// 所需的getter/setter方法
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getBookName() { return bookName; }
public void setBookName(String bookName) { this.bookName = bookName; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public Integer getCount() { return count; }
public void setCount(Integer count) { this.count = count; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
public String getPublish() { return publish; }
public void setPublish(String publish) { this.publish = publish; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public String getStatusCN() { return statusCN; }
public void setStatusCN(String statusCN) { this.statusCN = statusCN; }
}
// 1. Dao层:交给Spring管理(此前步骤已提取,此处保留完整)
@Component
public class BookDao {
public List<BookInfo> mockData() {
List<BookInfo> books = new ArrayList<>();
for (int i = 0; i < 5; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("书籍" + i);
book.setAuthor("作者" + i);
book.setCount(i * 5 + 3);
book.setPrice(new BigDecimal(new Random().nextInt(100)));
book.setPublish("出版社" + i);
book.setStatus(1);
books.add(book);
}
return books;
}
}
// 2. Service层改造前:纳入Spring但手动创建Dao(耦合)
@Component
public class BookService {
// 改造前:手动new依赖,强耦合
private BookDao bookDao = new BookDao();
public List<BookInfo> getBookList() {
List<BookInfo> books = bookDao.mockData();
for (BookInfo book : books) {
if (book.getStatus() == 1) {
book.setStatusCN("可借阅");
} else {
book.setStatusCN("不可借阅");
}
}
return books;
}
}
// 3. Service层改造后:删除手动创建,从Spring注入Dao(解耦)
// @Component
// public class BookService {
// // 改造后:通过@Autowired注入,解除耦合
// @Autowired
// private BookDao bookDao;
//
// public List<BookInfo> getBookList() {
// List<BookInfo> books = bookDao.mockData();
// for (BookInfo book : books) {
// if (book.getStatus() == 1) {
// book.setStatusCN("可借阅");
// } else {
// book.setStatusCN("不可借阅");
// }
// }
// return books;
// }
// }
// 4. Controller层:注入Service,对外提供接口
@RestController
@RequestMapping("/book")
public class BookController {
// 注入Spring管理的BookService
@Autowired
private BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList() {
// 调用Service层方法获取数据
List<BookInfo> books = bookService.getBookList();
return books;
}
}
- BookDao :
- 加
@Component注解被 Spring 管理,核心方法mockData()循环生成 5 条图书模拟数据,包含 id、书名、价格等字段;
- 加
- BookService(改造前) :
- 加
@Component纳入 Spring,但仍通过new BookDao()手动创建依赖,类间强耦合;
- 加
- BookService(改造后) :
- 删除
new BookDao()代码,新增@Autowired注解,从 Spring 容器注入 BookDao 对象,彻底解耦。
- 删除
1. 把 BookDao 交给 Spring 管理,由 Spring 来管理对象
java
@Component
public class BookDao {
/**
* 数据Mock 获取图书信息
* @return
*/
public List<BookInfo> mockData() {
List<BookInfo> books = new ArrayList<>();
for (int i = 0; i < 5; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("书籍" + i);
book.setAuthor("作者" + i);
book.setCount(i * 5 + 3);
book.setPrice(new BigDecimal(new Random().nextInt(100)));
book.setPublish("出版社" + i);
book.setStatus(1);
books.add(book);
}
return books;
}
}
2. 把 BookService 交给 Spring 管理,由 Spring 来管理对象
java
@Component
public class BookService {
private BookDao bookDao = new BookDao();
public List<BookInfo> getBookList() {
List<BookInfo> books = bookDao.mockData();
for (BookInfo book : books) {
if (book.getStatus() == 1) {
book.setStatusCN("可借阅");
} else {
book.setStatusCN("不可借阅");
}
}
return books;
}
}
3. 删除创建 BookDao 的代码,从 Spring 中获取对象
java
@Component
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getBookList() {
List<BookInfo> books = bookDao.mockData();
for (BookInfo book : books) {
if (book.getStatus() == 1) {
book.setStatusCN("可借阅");
} else {
book.setStatusCN("不可借阅");
}
}
return books;
}
}
4. 删除创建 BookService 的代码,从 Spring 中获取对象
java
@RequestMapping("/book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList(){
//获取数据
List<BookInfo> books = bookService.getBookList();
return books;
}
}
DI和IoC 之间的关系

- IoC(控制反转) :就像你说的,是把 "对象的创建和管理权" 交给了 "包工头"(Spring 容器)。以前是
Framework自己去 "找工人"(new Bottom()),现在是 "包工头"(Spring)统一管理所有 "工人"(Bean 对象),Framework不再操心工人从哪来,只需要等着包工头分配。 - DI(依赖注入) :就是 "包工头"(Spring)把管理好的 "工人"(
Bottom对象),直接 "派到"Framework这里来干活。Framework只需要在构造方法里 "接收" 这个工人,而不用自己去找。
所以,两者的关系是:
- IoC 是目标和思想:把控制权从类内部反转到外部容器,实现解耦。
- DI 是实现 IoC 的手段:通过构造方法、Setter 或字段注入等方式,由容器将依赖对象提供给需要它的类。
用你这个 "包工头和工人" 的比喻来对应代码:
- IoC :
Bottom对象的创建和管理权,从Framework类内部,反转到了 Spring 容器(包工头)那里。 - DI :Spring 容器(包工头)把已经创建好的
Bottom对象(工人),通过Framework的构造方法,注入到Framework实例中,让它可以直接使用。