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的一种实现.