Spring核心原理的快速入门:快速了解IoC与DI

IoC

IoC:Inversion of Control(控制反转)

Spring是一个包含了众多工具的IoC容器(即bean:spring管理的对象),也就是说Spring 是一个"控制反转 "的容器。

之前是对象本身管理自己的生命周期等等,现在交给spring来管理对象的生命周期

IoC介绍

1.2.1 传统程序开发

传统程序开发:一个对象(类)的创建,通常由调用者(使用者)来负责创建的,比如我们创建一个Car对象,通常由Framework来创建,Framework创建Car对象时,如果Car对象需要依赖其他对象(比如Bottom,或者Tire),那么这些依赖对象也需要Framework来创建,并注入给Car对象。

传统程序开发流程示意图:

传统程序开发代码示例:

java 复制代码
public class NewCarExample {
    public static void main(String[] args) {
        // 传统方式:直接创建Car对象
        Car car = new Car();
        car.run();
    }
}

// Car.java
public class Car {
    private Framework framework;

    public Car() {
        // Car类内部负责创建其依赖的Framework对象
        framework = new Framework();
        System.out.println("Car init...");
    }

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

// Framework.java
public class Framework {
    private Bottom bottom;

    public Framework() {
        // Framework类内部负责创建其依赖的Bottom对象
        bottom = new Bottom();
        System.out.println("Framework init...");
    }

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

// Bottom.java
public class Bottom {
    private Tire tire;

    public Bottom() {
        // Bottom类内部负责创建其依赖的Tire对象
        this.tire = new Tire();
        System.out.println("Bottom init...");
    }

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

// Tire.java
public class Tire {
    private int size;

    public Tire() {
        this.size = 17;
        System.out.println("轮胎尺寸: " + size);
    }

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

1.2.2 问题分析

在上面的代码中,我们发现Car对象对Framework类、Bottom类、Tire类有很强的依赖关系。如果这些依赖关系中的任何一个发生变化,比如Car类中需要增加一个依赖对象,或者某个依赖对象的创建方式发生变化(比如Tire需要进行自定义的Size,那么构造方法就需要修改,导致依赖他的所有方法的构造函数都需要修改),那么Car类也需要进行修改。这使得代码的耦合度很高,不利于代码的维护和扩展。

具体来说,我们看以下几点:

  1. Framework类对Car类的依赖: Framework类在main方法中直接创建了Car对象。如果Car类的构造函数发生变化,Framework类也需要修改。这同样导致了高耦合。
  2. 依赖的传递性: 如果Car类又依赖了其他类,那么这些其他类也需要Framework来创建,并注入给Car对象。这样一来,Framework类就成了整个应用程序的"大管家",它需要知道所有对象的创建细节,这使得Framework类变得臃肿且难以维护。
  3. 单元测试困难: 由于Car类直接创建了Framework对象,当对Car类进行单元测试时,很难替换掉Framework的真实实现(例如,使用模拟对象)。这使得单元测试变得复杂。
  4. 代码复用性差: 由于Car类与其需要依赖的类紧密耦合,Car类很难在其他不包含这些被依赖的类的场景下复用。

1.2.3 解决方案

在上面的解决方案中,我们看到针对传统程序设计模式的缺点,我们可以引入IoC(Inversion of Control) 思想,即控制反转。IoC的核心思想是:对象的创建和依赖关系的维护不再由调用者负责,而是由一个外部的容器来负责。当调用者需要某个对象时,不再自己去创建,而是向容器请求,容器会负责创建并提供给调用者。

这就像是:以前你饿了,需要自己去厨房做饭(自己创建对象),现在你饿了,只需要告诉餐厅你要什么菜(向容器请求对象),餐厅会帮你做好并送过来(容器创建并提供对象)。这样,你就不需要关心做饭的细节,只需要关心吃什么。

IoC控制反转流程示意图:

通过IoC,我们实现了控制反转,将对象的创建和依赖注入的控制权从应用程序代码中移出,交给了IoC容器。这样,应用程序代码与它所依赖的对象之间的耦合度就大大降低了。

IoC容器就像一个中央工厂,负责管理所有对象的生命周期和依赖关系。当应用程序需要某个对象时,只需向IoC容器声明其需求,IoC容器就会负责创建该对象及其所有依赖,并将其注入到应用程序中。

IoC容器示意图:

IoC容器的好处有:

  1. 降低耦合度: 对象之间不再直接依赖,而是通过IoC容器进行解耦。当一个类的依赖发生变化时,不需要修改该类本身的代码,只需要修改IoC容器的配置。这使得代码更加灵活,易于维护和扩展。
  2. 提高代码复用性: 对象不再负责创建自己的依赖,因此可以更容易地在不同的场景下复用。例如,Car类不再其依赖类的创建细节,它可以与任何实现了其依赖类的接口的对象一起使用。
  3. 提高可测试性: 在单元测试中,可以轻松地替换掉真实的对象,使用模拟对象进行测试。这使得单元测试更加简单和高效。
  4. 简化配置: IoC容器可以集中管理所有对象的创建和依赖关系,从而简化了应用程序的配置。开发人员不再需要手动管理大量的对象创建代码。

1.2.4 IoC程序开发

基于以上思想,我们尝试用代码来表示IoC到底是怎么一回事,特别注意受控方式,因为是IoC的关键所在。

首先我们先修改一下:

java 复制代码
// NewCarExample.java
public class NewCarExample {
    public static void main(String[] args) {
        // IoC方式:由外部(模拟IoC容器)创建并注入依赖
        Tire tire = new Tire();
        Bottom bottom = new Bottom(tire); // Bottom依赖Tire
        Framework framework = new Framework(bottom); // Framework依赖Bottom
        Car car = new Car(framework); // Car依赖Framework
        car.run();
    }
}

// Car.java
public class Car {
    private Framework framework;

    // 通过构造函数注入依赖
    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("Car create...");
    }

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

// Bottom.java
public class Bottom {
    private Tire tire;

    // 通过构造函数注入依赖
    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("Bottom create...");
    }

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

// Tire.java
public class Tire {
    public Tire() {
        System.out.println("Tire create...");
    }

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

// Framework.java
public class Framework {
    private Bottom bottom;

    // 通过构造函数注入依赖
    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("Framework create...");
    }

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

代码如上图所示,我们看到,各个类之间不再直接依赖,而是通过构造器注入的方式,将依赖对象从外部传入。这使得各个类之间的耦合度大大降低了,并且可以非常容易地进行替换(模拟或真实对象)。当然离真正的IoC容器还有很长的路要走,但思想已经非常接近了。

现在我们再来理一下,Car依赖FrameworkFramework依赖BottomBottom依赖Tire,那么整个依赖关系是:

依赖关系与IoC容器示意图:
Tire Bottom Framework Car IoC容器

IoC容器是一个独立的模块,它负责创建和管理所有的对象。当一个对象需要另一个对象时,它不再自己去创建,而是向IoC容器请求。IoC容器会负责创建所需的对象,并将它们注入到请求对象中,创建实例的时候不需要了解其中的细节, 降低了使用双方的的依赖程度,这样,对象之间就解耦了。

FrameworkBottomTireCar现在都变成了"被动"的对象,它们不再主动去创建自己的依赖对象,而是等待IoC容器将依赖对象注入进来。这种"被动"的特性就是IoC的核心思想。

IoC容器就像一个中央工厂,负责管理所有对象的生命周期和依赖关系。当应用程序需要某个对象时,只需向IoC容器声明其需求,IoC容器就会负责创建该对象及其所有依赖,并将其注入到应用程序中。

DI

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

从这一点来看,依赖注入(DI)和控制逆转(IoC)是从不同的角度所描述的同一件事,依赖注入是

从应用程序的角度来描述,指通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦

简单使用

@Component 的作用

@Component 就像给你的类贴上一个"标签",告诉Spring: "嘿,Spring!我是一个组件,请你管理我,并且在需要的时候,可以把我的实例提供给别人。"

当Spring扫描到带有@Component注解的类时,它就会:

  1. 创建并管理这个类的实例(对象)
  2. 将这个实例放入它的"容器"中,随时准备被其他地方使用。

@Autowired 的作用

@Autowired 就像一个"请求",告诉Spring: "嘿,Spring!我这里需要一个你管理的某个类型的对象(比如一个BookDao),请你把它"送"给我!"

Spring收到这个请求后,就会从它管理的众多组件中找到一个匹配的,然后自动把它赋值给你的变量。

结合 @Component@Autowired 的DI流程

  1. 定义组件(@Component:

    java 复制代码
    @Component // 告诉Spring:我是BookDao,请你管理我
    public class BookDao {
        // ... 提供数据的方法
    }
    
    @Component // 告诉Spring:我是BookService,请你管理我
    public class BookService {
        // ... 处理业务逻辑的方法
    }

现在,BookDaoBookService 的实例都由Spring创建和管理了。

  1. 注入依赖(@Autowired:

    java 复制代码
    @Component
    public class BookService {
        @Autowired // 告诉Spring:我需要一个BookDao,请你给我
        private BookDao bookDao; // Spring会自动把BookDao的实例赋值给它
        // ...
    }
    
    @RestController // @RestController也包含了@Component的功能,所以Spring也会管理它
    public class BookController {
        @Autowired // 告诉Spring:我需要一个BookService,请你给我
        private BookService bookService; // Spring会自动把BookService的实例赋值给它
        // ...
    }
    • BookService 不再自己 new BookDao(),而是声明它需要一个 BookDao,Spring会注入进来。
    • BookController 不再自己 new BookService(),而是声明它需要一个 BookService,Spring会注入进来。

最终效果: 各个类(BookControllerBookServiceBookDao)之间不再直接创建对方的实例,而是通过Spring这个"中间人"来获取它们需要的依赖。这使得代码:

  • 更松散:类与类之间不再紧密耦合。
  • 更灵活:可以轻松替换依赖的实现。
  • 更容易测试:测试时可以注入模拟的依赖。

其他文章:
IoC详细介绍:

https://blog.csdn.net/fouryears_23417/article/details/149261186?fromshare=blogdetail\&sharetype=blogdetail\&sharerId=149261186\&sharerefer=PC\&sharesource=fouryears_23417\&sharefrom=from_link

相关推荐
重生之后端学习15 分钟前
day08-Elasticsearch
后端·elasticsearch·spring cloud·中间件·全文检索·jenkins
东阳马生架构29 分钟前
订单初版—5.售后退货链路中的技术问题说明文档
java
小小寂寞的城34 分钟前
JAVA策略模式demo【设计模式系列】
java·设计模式·策略模式
志辉AI编程1 小时前
别人还在入门,你已经精通!Claude Code进阶必备14招
后端·ai编程
JAVA学习通1 小时前
图书管理系统(完结版)
java·开发语言
代码老y1 小时前
Spring Boot项目中大文件上传的高级实践与性能优化
spring boot·后端·性能优化
abigalexy1 小时前
深入Java锁机制
java
paishishaba1 小时前
处理Web请求路径参数
java·开发语言·后端
神仙别闹1 小时前
基于Java+MySQL实现(Web)可扩展的程序在线评测系统
java·前端·mysql
程序无bug1 小时前
Java中的8中基本数据类型转换
java·后端