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 实例中,让它可以直接使用。
相关推荐
Irissgwe2 小时前
基础I/O
java·linux·前端
木易 士心2 小时前
Java中 synchronized 和 volatile 详解
java·开发语言·jvm
小码狐2 小时前
Spring相关知识【知识整理】
java·后端·spring
巫山老妖2 小时前
多 Agent 协作实战:我用 3 只龙虾组了个「AI小分队」,效率直接翻倍
java·前端
xienda2 小时前
Spring Boot 核心定义与用处
java·spring boot·后端
直有两条腿2 小时前
【Spring Boot】原理
java·spring boot·后端
一只叫煤球的猫2 小时前
用这个框架彻底摆脱Controller,从此专注业务——ArcRoute
java·spring·开源
SunnyDays10112 小时前
Java 如何根据模板高效生成Word文档
java·根据模板生成word文档·生成word文档
攀岩巨峰的程序猿2 小时前
代码开发过程中涉及到bean的copy方法梳理
java