目录
- IoC & DI ⼊⻔
- 什么是Spring
- 什么是容器
- 什么是IoC
- IoC介绍
- 传统程序开发
- 问题分析
- 解决方案
- IoC程序开发
- IoC优势
IoC & DI ⼊⻔
IoC:Inversion of Control (控制反转)
DI:Dependency Injection
在前⾯我们学习了Spring Boot和Spring MVC的开发, 可以完成⼀些基本功能的开发了, 但是什么是Spring呢? Spring, Spring Boot 和SpringMVC⼜有什么关系呢?
我们先看什么是Spring
什么是Spring
通过前⾯的学习, 我们知道了Spring是⼀个开源框架, 他让我们的开发更加简单. 他⽀持⼴泛的应⽤场景, 有着活跃⽽庞⼤的社区, 这也是Spring能够⻓久不衰的原因.
但是这个概念相对来说, 还是⽐较抽象.
Spring两大核心思想:
- IoC
- AOP
我们⽤⼀句更具体的话来概括Spring, 那就是: Spring 是包含了众多⼯具⽅法的 IoC 容器
什么是容器?什么是 IoC 容器?接下来我们⼀起来看
什么是容器
容器是⽤来容纳某种物品的(基本)装置。
来⾃:百度百科
⽣活中的⽔杯, 垃圾桶, 冰箱等等这些都是容器.
我们想想,之前我们接触的容器有哪些?
- List/Map -> 数据存储容器
- Tomcat -> Web 容器
Spring容器,装的是 IoC 对象
什么是IoC
IoC 是Spring的核⼼思想, 也是常⻅的⾯试题, 那什么是IoC呢?
其实IoC我们在前⾯已经使⽤了, 我们在前⾯讲到, 在类上⾯添加 @RestController
和 @Controller
注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想.
IoC: Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器.
什么是控制反转呢? 也就是控制权反转. 什么的控制权发⽣了反转? 获得依赖对象的过程被反转了,交给了Spring
也就是说, 当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊ (Dependency Injection,DI) 就可以了.
这个容器称为:IoC容器. Spring是⼀个IoC容器, 所以有时Spring 也称为Spring 容器
控制反转是⼀种思想, 在⽣活中也是处处体现.
⽐如⾃动驾驶, 传统驾驶⽅式, ⻋辆的横向和纵向驾驶 控制权由驾驶员来控制, 现在交给了驾驶⾃动化系统来控制, 这也是控制反转思想在⽣活中的实现.
⽐如招聘, 企业的员⼯招聘,⼊职, 解雇等控制权, 由⽼板转交给给HR(⼈⼒资源)来处理
IoC介绍
接下来我们通过案例来了解⼀下什么是IoC
需求: 造⼀辆⻋
传统程序开发
我们的实现思路是这样的:
先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦.
最终程序的实现代码如下:
//Main.java
public class Main {
public static void main(String[] args) {
Car car=new Car();
car.run();
}
}
//Car.java
public class Car {
private FrameWork frameWork;
public Car() {
frameWork=new FrameWork();
System.out.println("car init...");
}
public void run(){
System.out.println("car run...");
}
}
//FrameWork.java
public class FrameWork {
private Bottom bottom;
public FrameWork() {
bottom=new Bottom();
System.out.println("frameWork init...");
}
}
//Bottom.java
public class Bottom {
private Tire tire;
public Bottom(){
tire=new Tire();
System.out.println("bottom init...");
}
}
//Tire.java
public class Tire {
private int size=17;
public Tire(){
System.out.println("tire init...");
}
}
问题分析
这样的设计看起来没问题,但是可维护性却很低.
接下来需求有了变更: 随着对的⻋的需求量越来越⼤, 个性化需求也会越来越多,我们需要加⼯多种尺⼨的轮胎.
那这个时候就要对上⾯的程序进⾏修改了,修改后的代码如下所⽰:
修改之后, 其他调⽤程序也会报错, 我们需要继续修改
完整代码如下:
public class Main {
public static void main(String[] args) {
Car car=new Car(20);
car.run();
Car car2=new Car(21);
car2.run();
}
}
public 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...");
}
}
public class FrameWork {
private Bottom bottom;
public FrameWork(int size) {
bottom=new Bottom(size);
System.out.println("frameWork init...");
}
}
public class Bottom {
private Tire tire;
public Bottom(int size){
tire=new Tire(size);
System.out.println("bottom init...");
}
}
public class Tire {
private int size;
public Tire(int size){
this.size=size;
System.out.println("tire init...size:"+size);
}
}
从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修改.
程序的耦合度⾮常⾼(修改⼀处代码, 影响其他处的代码修改)
即一辆车需要知道车身是怎么样的,车身需要知道底盘是怎么样的,底盘需要知道轮子是怎么样的,耦合度就很高
解决方案
在上⾯的程序中, 我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改. 同样因为我们是根据底盘设计的⻋⾝,那么⻋⾝也得改,同理汽⻋设计也得改, 也就是整个设计⼏乎都得改
我们尝试换⼀种思路, 我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计底盘,最后根据底盘来设计轮⼦. 这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝,⻋⾝依赖汽⻋
这就类似我们打造⼀辆完整的汽⻋, 如果所有的配件都是⾃⼰造,那么当客⼾需求发⽣改变的时候,⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那么即使是轮胎的尺⼨发⽣改变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃⾝是不需要出⼒的.
如何来实现呢:
我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃⼰也要跟着修改.
此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修改任何代码,这样就完成了程序的解耦.
IoC程序开发
基于以上思路,我们把调⽤汽⻋的程序⽰例改造⼀下,把创建⼦类的⽅式,改为注⼊传递的⽅式.
具体实现代码如下:
public class Main {
public static void main(String[] args) {
Tire tire=new Tire(17,"red");
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("car 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("frameWork init...");
}
}
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("bottom init...");
}
}
public class Tire {
private int size;
private String color;
public Tire(int size,String color) {
this.size = size;
this.color=color;
System.out.println("tire init...size:"+size+",color:"+color);
}
}
代码经过以上调整,⽆论底层类如何变化,整个调⽤链是不⽤做任何改变的,这样就完成了代码之间的解耦,从⽽实现了更加灵活、通⽤的程序设计了。
汽车只需要知道它需要一个车身,车身只需要知道它需要一个底盘,底盘只需要知道它需要轮子,不需要知道具体细节,就解耦合了
传统开发方法:自己做饭
想象一下,你每天晚上都要自己做饭吃。这个过程就类似于传统开发方法:
- 自行准备:你需要自己购买食材(类似于创建对象),准备调料,做每一道菜。
- 高耦合:每个步骤都依赖前一个步骤的完成,如果你忘了买某样食材,整个晚餐都可能无法完成。
- 灵活性差:如果突然有朋友来访,而你准备的食材和菜谱是固定的,很难快速适应增加的人数或特殊的饮食需求。
- 难以更换:一旦你决定做某个菜,就必须按照原计划进行,中途改变计划将会非常麻烦。
IoC(依赖注入)方法:外卖服务
现在,想象你改为使用一家提供全套餐服务的外卖(这就像是依赖注入):
- 服务提供:你只需要选择想吃什么,外卖服务提供完整的餐点,你不需要自己准备食材或调料。
- 解耦:你不依赖于具体的购物或烹饪过程,只需等待成品送达。
- 灵活性高:突然有更多人需要吃饭?只需增加订单数量或更改订单即可,无需自己重新准备。
- 易于更换:想改变菜单?只需取消当前订单,重新下一个新的订单即可。
IoC优势
在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire
改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car
我们发现了⼀个规律,通⽤程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了 Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由当前类控制了.
这样的话, 即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。
到这⾥, 我们⼤概就知道了什么是控制反转了, 那什么是控制反转容器呢?也就是IoC容器
这部分代码, 就是IoC容器做的⼯作.
从上⾯也可以看出来, IoC容器具备以下优点:
资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度。
-
资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC容器中去取就可以了
-
我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度.
Spring 就是⼀种IoC容器, 帮助我们来做了这些资源管理.
Spring帮我们管理对象,我们要做的:
- 告诉Spring帮我们管理哪些对象(存)
- 知道如何取出这些对象(取)