Spring IoC & DI(1)

IoC & DI入门

Spring

通过前面的学习, 我们知道了Spring是一个开源框架, 它让我们的开发更加简单. 它支持广泛的应用场景, 有着活跃且庞大的社区, 这就是Spring能够长久不衰的原因.

但是这个概念还是比较抽象.

可以用更具体的话描述Spring, 那就是: Spring是包含了众多工具方法的IoC容器.

那问题来力, 什么是容器? 什么是IoC容器? 

什么是容器

容器是用来容纳某种物品的(基本)装置.

我们想想, 之前接触的容器有哪些?

List/Map -> 数据存储容器

Tomcat -> Web容器

什么是IoC

IoC是Spring的核心思想, 也是常见的面试题, 那什么是IoC呢? 

IoC我们已经使用了, 我们在前面讲到, 在类上面添加@RestController 和@Controller注解,就是把这个对象交给Spring管理, Spring框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想.

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

什么是控制反转呢?也就是控制权反转. 什么的控制权发生了反转? 获得依赖对象的过程被反转了. 也就是说, 当需要某个对象时, 传统开发模式中只需要自己通过new创建对象, 现在不需要再进行创建, 把创建对象的任务交给容器, 程序中只需要依赖注入就可以了.

这个容器称为: IoC容器. Spring是一个IoC容器, 所以有时Spring也称为Spring容器.

控制反转是一种思想, 在生活中也处处体现.

当人们斗地主时, 如果手里只剩下王炸, 可以不用管了, 整个托管即可.

在自动驾驶中, 驾驶员可以掌握驾驶的控制权, 也可以将这个控制权交给自动化驾驶系统.

IoC介绍

接下来我们通过案例来了解一下什么是IoC.

需求: 造一辆车

传统程序开发

我们的实现思路是这样的:

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

 最终实现的代码如下:

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() {
            this.bottom = new Bottom();
            System.out.println("Frame 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);
        }
    }
}

问题分析

这样的设计看起来没问题, 但是可维护性却很低.

接下来需求有了变更: 随着对车的需求量越来越大, 个性化需求也越来越多, 我们需要加工多种尺寸的轮胎.

那这个时候就要对上面的程序进行修改了, 修改后的代码如下:

 

修改之后, 其它调用程序也会报错, 我们需要修改继续修改(即每一个构造方法都要传一个size) 

完整代码如下:

public class NewCarExample {
    public static void main(String[] args) {
        Car car = new Car(20);
        car.run();
    }

    /**
     * 汽车对象
     */
    static class Car {
        private FrameWork frameWork;

        public Car(int size) {
            frameWork = new FrameWork(size);
            System.out.println("Car init...");
        }

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

    /**
     * 车身类
     */
    static class FrameWork {
        private Bottom bottom;

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

    /**
     * 底盘类
     */
    static class Bottom {
        private Tire tire;

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

    /**
     * 轮胎类
     */
    static class Tire {
        //尺寸
        private int size;

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

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

程序的耦合度非常高(修改一处代码, 影响其它处代码的修改).

问题解决

在上面的程序当中, 我们是根据轮子的尺寸设计底盘, 轮子尺寸一改, 底盘的设计就得修改. 同样因为我们是根据底盘设计的车身, 那么车身也得修改, 同理汽车设计也得修改, 也就是整个设计都会改.

我们尝试换一种思路, 我们先设计汽车的大概样子, 然后根据汽车的样子来设计车身, 根据车身来设计底盘, 最后根据底盘来设计轮子, 这时, 依赖关系就倒置过来了: 轮子依赖底盘, 底盘依赖车身, 车身依赖汽车.

如何来实现呢?

我们可以尝试不在每个类中创建自己的下级类, 如果自己创建下级类就会出现下级类改变操作, 自己也要跟着修改.

此时, 我们只需要将原来由自己创建的下级类, 改为传递的方式(也就是注入的方式), 因为我们不需要在当前类中创建下级类了, 所以下级类即使发生变化(创建或者减少参数), 当前类不用再改变代码了, 这就实现了程序的解耦.

public class NewCarExample1 {

    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 trie) {
            this.tire = trie;
            System.out.println("Bottom init...");
        }
    }

    static class Tire {
        private int size;

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

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

 IoC优势

在传统的代码中对象的创建对象的顺序是: Car -> FrameWork -> Bottom ->  Tire

改进之后解耦的代码的对象的创建顺序是: Tire -> Bottom -> FrameWork -> Car

我们发现一个规律, 通用程序的实现代码, 类的创建顺序是反的, 传统代码是Car控制并创建了FrameWork, 依次向下, 而改进之后的控制权发生了反转,  不再是使用方对象创建并控制依赖对象了, 而是把依赖对象注入到当前对象中, 依赖对象的控制权不再由当前类控制了.

因此即使依赖类发生任何改变, 当前类都是不受影响的, 这就是典型的控制反转, 也就是IoC的实现思想.

学到这里, 我们就大概知道什么是控制反转了, 那什么是控制反转容器呢,也就是IoC容器.

 

这部分代码就是IoC容器所做的工作.

从上面也可以看出, IoC具有以下优点:

资源不再由资源的双方管理, 而由不使用资源的第三方管理, 这可以带来很多好处.

第一: 资源的集中管理, 实现资源的可配置和易管理, 用的时候只需要从IoC容器中取即可.

第二:降低了使用资源双方的依赖程度, 也就是我们说的耦合度.

DI介绍

DI:Dependency Injection(依赖注入).

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

程序运行时需要某个资源, 容器就可以提供这个资源.

从这点来看, IoC(控制反转)和DI(依赖注入)是从不同角度描述同一件事情, 就是指通过引入IoC容器, 利用依赖关系注入的方式, 实现对象之间的解耦.

之前的代码中, 就是通过构造函数的方式, 将依赖的对象注入到需使用对象中.

 

DI是IoC的一种实现. 

相关推荐

  1. nvm1.1.11

    2024-03-26 00:16:05       31 阅读
  2. 1.下午试题1

    2024-03-26 00:16:05       10 阅读
  3. HTML-1

    2024-03-26 00:16:05       35 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-26 00:16:05       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-26 00:16:05       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-26 00:16:05       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-26 00:16:05       20 阅读

热门阅读

  1. HarmonyOS系统开发ArkTS常用组件编程技巧

    2024-03-26 00:16:05       17 阅读
  2. KMP算法

    KMP算法

    2024-03-26 00:16:05      36 阅读
  3. ARM-IIC实验

    2024-03-26 00:16:05       20 阅读
  4. vuetify3 弹窗中使用 element-plus 时间控件异常解决

    2024-03-26 00:16:05       23 阅读
  5. leetcode 322.零钱兑换

    2024-03-26 00:16:05       20 阅读
  6. Docker常用命令

    2024-03-26 00:16:05       24 阅读
  7. 2299. 强密码检验器 II

    2024-03-26 00:16:05       23 阅读
  8. 数据建模与PASS层

    2024-03-26 00:16:05       22 阅读