Spring IoC 与 DI 思想及实践详解

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 容器具备以下优点:

资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。

  1. 资源集中管理: IoC 容器会帮我们管理一些资源 (对象等),我们需要使用时,只需要从 IoC 容器中去取就可以了
  2. 我们在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合度。

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 层的解耦步骤:

  1. Service 层及 Dao 层的实现类,交给 Spring 管理:使用注解:@Component
  2. 在 Controller 层和 Service 注入运行时依赖的对象:使用注解 @Autowired

实现:

  1. 把 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 或字段注入等方式,由容器将依赖对象提供给需要它的类。

用你这个 "包工头和工人" 的比喻来对应代码:

  1. IoCBottom 对象的创建和管理权,从 Framework 类内部,反转到了 Spring 容器(包工头)那里。
  2. DI :Spring 容器(包工头)把已经创建好的 Bottom 对象(工人),通过 Framework 的构造方法,注入到 Framework 实例中,让它可以直接使用。
相关推荐
小怪吴吴5 小时前
idea 开发Android
android·java·intellij-idea
嘻嘻哈哈樱桃5 小时前
牛客经典101题题解集--动态规划
java·数据结构·python·算法·职场和发展·动态规划
一次旅行5 小时前
IDEA安装CC GUI新手指南
java·ide·intellij-idea
超梦dasgg5 小时前
Spring AI 智能航空助手项目实战
java·人工智能·后端·spring·ai编程
counting money5 小时前
Spring框架基础(配置篇)
java·后端·spring
秋96 小时前
OceanBase与GreatSQL在Java应用中的性能调优方法有哪些?
java·开发语言·oceanbase
今天又在写代码6 小时前
并发问题解决
java·开发语言·数据库
老王以为6 小时前
前端视角下的 Java
java·javascript·程序员
看腻了那片水7 小时前
开源一个对业务代码零侵入的透明数据治理框架 —— 【sangsang】
java·mybatis