【SpringBoot】从零开始全面解析Spring Ioc&DI (一)

本篇博客给大家带来的是SpringBoot的知识点, 本篇介绍Spring IoC 和 DI 相关知识.
🐎文章专栏: JavaEE进阶
🚀若有问题 评论区见
❤ 欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .

王子,公主请阅🚀

  • 要开心
  • [1. loC & DI 入门](#1. loC & DI 入门)
    • [1.1 Spring 是什么?](#1.1 Spring 是什么?)
      • [1.1.1 什么是容器?](#1.1.1 什么是容器?)
      • [1.1.2 什么是 IoC?](#1.1.2 什么是 IoC?)
    • [1.2 IoC 具体介绍](#1.2 IoC 具体介绍)
      • [1.2.1 传统程序开发](#1.2.1 传统程序开发)
      • [1.2.2 问题分析](#1.2.2 问题分析)
      • [1.2.3 解决方案](#1.2.3 解决方案)
      • [1.2.4 IoC 优势](#1.2.4 IoC 优势)
    • [1.3 DI 介绍](#1.3 DI 介绍)
  • [2. loC & DI 使用](#2. loC & DI 使用)

要开心

要快乐

顺便进步

1. loC & DI 入门

1.1 Spring 是什么?

通过前面的学习, 我们知道了Spring是一个开源框架, 他让我们的开发更加简单. 他支持广泛的应用场景, 有着活跃而庞大的社区, 这也是Spring能够长久不衰的原因.
但是这个概念相对来说, 还是比较抽象.
我们用一句更具体的话来概括Spring, 那就是: Spring 是包含了众多工具方法的 IoC 容器 。
那问题来了,什么是 IoC 容器?接下来我们一起来看 .

1.1.1 什么是容器?

容器是用来容纳某种物品的(基本)装置。⸺来自:百度百科
生活中的水杯, 垃圾桶, 冰箱等等这些都是容器.
之前接触的容器有哪些?
List/Map -> 数据存储容器
Tomcat -> Web 容器

1.1.2 什么是 IoC?

这个问题也是常见的面试题。
IoC 是Spring的核心思想. 其实我们在上篇文章就已经使用过 IoC思想了.

在类上面添加 @RestController 和
@Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想。

IoC: Inversion of Control (控制反转), 也就是说 Spring 是一个"控制反转"的容器.

控制反转就是 控制权反转. 获得依赖对象的过程被反转了。
也就是说, 当需要某个对象时, 传统开发模式中需要自己通过 new 创建对象, 现在不需要再进行创建, 把创建对象的任务交给容器, 程序中只需要依赖注入 (Dependency Injection,DI)就可以了.
这个容器称为:IoC容器. Spring是一个IoC容器, 所以有时Spring 也称为Spring 容器.

1.2 IoC 具体介绍

通过案例来了解以下什么是 IoC.
需求: 造一辆车.

1.2.1 传统程序开发

传统实现思路是这样的:
先设计轮子(Tire),然后根据轮子的大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car)。这里就出现了一个"依赖"关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子.

最终程序实现代码如下:

java 复制代码
1 public class NewCarExample {
2 public static void main(String[] args) {
3 Car car = new Car();
4 car.run();
5 }
6
7 /**
8 * 汽⻋对象
9 */
10 public class Car {
11 private Framework framework;
12
13 public Car() {
14 framework = new Framework();
15 System.out.println("Car init....");
16 }
17 public void run(){
18 System.out.println("Car run...");
19 }
20 }
21
22 /**
23 * ⻋⾝类
24 */
25 public class Framework {
26 private Bottom bottom;
27
28 public Framework() {
29 bottom = new Bottom();
30 System.out.println("Framework init...");
31 }
32 }
33
34 /**
35 * 底盘类
36 */
37 public  class Bottom {
38 private Tire tire;
39
40 public Bottom() {
41 this.tire = new Tire();
42 System.out.println("Bottom init...");
43 }
44 }
45
46 /**
47 * 轮胎类
48 */
49 public class Tire {
50 // 尺⼨
51 private int size;
52
53 public Tire(){
54 this.size = 17;
55 System.out.println("轮胎尺⼨:" + size);
56 }
57 }
58 }

1.2.2 问题分析

这样的设计看起来没问题,但是可维护性却很低.
如果需求有了变更: 随着对的车的需求量越来越大, 个性化需求也会越来越多,我们需要加工多种尺寸的轮胎。

java 复制代码
public class Tire {
    private int size;

    public Tire(int size) {
        System.out.println("tire size:"+this.size);
    }
}

修改之后, 其他调用程序也会报错, 我们需要继续修改:

java 复制代码
public class Bottom {
    private Tire tire;

    public Bottom(int size) {
        tire = new Tire(size);
        System.out.println("tire init...");
    }
}

public class Framework {
    private Bottom bottom;

    public Framework(int size) {
        bottom = new Bottom(size);
        System.out.println("bottom init...");
    }
}

public class Car {
    private Framework framework;
    public Car(int size) {
        framework = new Framework(size);
        this.framework = framework;
        System.out.println("framework init...");
    }

    public void run() {
        System.out.println("car run...");
    }
}

从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调用链上的所有代码都需要
修改. 程序的耦合度非常高.

1.2.3 解决方案

如果自己创建下级类就会出现当下级类发生改变,自己也要跟着修改. 因此我们可以尝试不在每个类中创建下级类。
只需要将原来由自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦.

java 复制代码
public class Main {
    public static void main(String[] args) {
//        Car car = new Car();
//        car.run();
        Tire tire = new Tire(15);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}

public class Car {
    private Framework framework;

    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("framework init...");
    }

    public void run() {
        System.out.println("car run...");
    }
}

public class Framework {
    private Bottom bottom;

    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("bottom init...");
    }
}

public class Bottom {
    private Tire tire;

    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("tire init...");
    }
}

public class Tire {
    private int size;

    public Tire(int size) {
        System.out.println("tire size:"+this.size);
    }
}

代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何改变的,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计。 这就是 IoC思想.

1.2.4 IoC 优势

在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire
改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car

通用程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了 Framework,Framework 控制并创建了 Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了.
这样的话, 即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。

从上面也可以看出来, IoC容器具备以下优点:
① 资源不由使用资源的双方管理,而由不使用资源的第三方管理,资源集中管理, 实现资源的可配置和易管理。

② 降低了使用资源双方的依赖程度,也就是耦合度。

1.3 DI 介绍

DI: Dependency Injection(依赖注入)

容器在运行期间, 动态的为应用程序提供运行时所依赖的资源,称之为依赖注入。

当程序运行时需要某个资源,此时容器就为其提供这个资源.
从这点来看, 依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,就是指通过引 入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦。

上述代码中, 是通过构造函数的方式, 把依赖对象注入到需要使用的对象中的。

IoC 是一种思想,也是"目标", 而思想只是一种指导原则,最终还是要有可行的落地方案,而 DI 就属于具体的实现。所以也可以说, DI 是IoC的一种实现。

2. loC & DI 使用

既然 Spring 是一个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:
• 存
• 取

Spring 容器 管理的主要是对象, 这些对象, 我们称为"Bean". 我们把这些对象交由Spring管理, 由Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象。

打开上篇文章spring-book项目, 在service目录下创建BookService文件, 把BookDao, BookService 交给Spring管理, 完成Controller层, Service层, Dao层的解耦。

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

把BookDao 交给Spring管理, 由Spring来管理对象

java 复制代码
@Component
public class BookDao {
    public List<BookInfo> mockData() {
        //理论上该方法应该从数据库获取, 此处先 mock数据.
        List<BookInfo> bookInfos = new ArrayList<>();
        //mock(模拟) 数据
        for (int i = 1; i <= 15; i++) {
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("图书"+i);
            bookInfo.setAuthor("作者"+i);
            bookInfo.setNum(i*2+1);
            bookInfo.setPrice(new BigDecimal(i*3));
            bookInfo.setPublishName("出版社"+i);
            if(i % 5 == 0) {
                bookInfo.setStatus(2);
//                bookInfo.setStatusCN("不可借阅");
            }else {
                bookInfo.setStatus(1);
//                bookInfo.setStatusCN("可借阅");
            }
            bookInfos.add(bookInfo);
        }
        return bookInfos;
    }
}

把BookService 交给Spring管理, 由Spring来管理对象

java 复制代码
@Service
public class BookService {
    public List<BookInfo> getBookList() {
        BookDao bookDao = new BookDao();
        List<BookInfo> bookInfos = bookDao.mockData();
        for(BookInfo bookInfo : bookInfos) {
            if(bookInfo.getStatus() == 2) {
                bookInfo.setStatusCN("不可借阅");
            }else {
                bookInfo.setStatusCN("可借阅");
            }
        }
        return bookInfos;
    }
}

删除创建BookDao的代码, 从Spring中获取对象

java 复制代码
@Component
public class BookService {
    @Autowired
    private BookDao bookDao;
    public List<BookInfo> getBookList() {
//        BookDao bookDao = new BookDao();
        List<BookInfo> bookInfos = bookDao.mockData();
        for(BookInfo bookInfo : bookInfos) {
            if(bookInfo.getStatus() == 2) {
                bookInfo.setStatusCN("不可借阅");
            }else {
                bookInfo.setStatusCN("可借阅");
            }
        }
        return bookInfos;
    }
}

删除创建BookService的代码, 从Spring中获取对象

java 复制代码
@RequestMapping("/book")
@RestController
public class BookController {
//    @RequestMapping("/getBookList")
//    public List<BookInfo> getBookList() {
//        List<BookInfo> bookInfos = new ArrayList<>();
//        //mock(模拟) 数据
//        for (int i = 1; i <= 15; i++) {
//            BookInfo bookInfo = new BookInfo();
//            bookInfo.setId(i);
//            bookInfo.setBookName("图书"+i);
//            bookInfo.setAuthor("作者"+i);
//            bookInfo.setNum(i*2+1);
//            bookInfo.setPrice(new BigDecimal(i*3));
//            bookInfo.setPublishName("出版社"+i);
//            if(i % 5 == 0) {
//                bookInfo.setStatus(2);
//                bookInfo.setStatusCN("不可借阅");
//            }else {
//                bookInfo.setStatus(1);
//                bookInfo.setStatusCN("可借阅");
//            }
//            bookInfos.add(bookInfo);
//        }
//        return bookInfos;
//    }
    @Autowired
    private BookService bookService;
    @RequestMapping("/getBookList")
    public List<BookInfo> getBookList() {
//        BookService bookService = new BookService();
        return bookService.getBookList();
    }
}

本篇博客到这里就结束啦, 感谢观看 ❤❤❤
🐎期待与你的下一次相遇😊😊😊

相关推荐
颜酱几秒前
02 | 搭骨架:用 LangGraph 编排 12 步工作流
前端·人工智能·后端
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅2 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
空中湖3 小时前
Spring AI Agent 编排:ReAct 模式 + 多 Agent 协作实战
人工智能·spring·react.js
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端