手撸Spring系列1:IOC/DI 思想(理论篇)

说在前头: 笔者本人为大三在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,发布的文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。

手撸Spring系列是笔者本人首次尝试的、较为规范的系列博客,将会围绕Spring框架分为 IOC/DI 思想Spring MVCAOP 思想Spring JDBC 四个模块,并且每个模块都会分为 理论篇源码篇实战篇 三个篇章进行讲解(大约12篇文章左右的篇幅)。从原理出发,深入浅出,一步步接触Spring源码并手把手带领大家一起写一个 迷你版的Spring框架,促进大家进一步了解Spring的本质!

由于源码篇涉及到源码的阅读,可能有小伙伴没有成功构建好Spring源码的阅读环境,笔者强烈建议:想要真正了解Spring,一定要构建好源码的阅读环境再进行研究,具体构建过程可查看笔者此前的博客:《如何构建Spring5源码阅读环境》

前言

今天的这一篇博客将会讲述 IOC/DI思想,作为手撸Spring系列的第一篇理论博客,笔者将会从Spring的核心思想IOC开始讲解。为了读者能够更好的理解文章中的内容,在上车前,笔者建议读者们能够拥有以下车票,以防走丢:有Spring使用经验(刚需)有SpringBoot使用经验(建议)使用Spring写过个人项目(建议)使用过MyBatis(建议)。检查好自己的车票后就自行上车吧!!
手撸Spring系列1:IOC/DI 思想(理论篇)_第1张图片

一、什么是IOC(控制反转)?

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。

简单的来说: 其实就是将原本需要程序员手动去new(实例化)的类对象,交给第三方,也就是交给我们的Spring去管理,由Spring帮助我们去实例化一个类。所谓的控制反转,就是将一个类的创建和初始化的控制权等操作,从程序员的手中交给第三方框架Spring去管理,从而使得程序员无需关心一个类是如何创建的,应该怎么实例化、初始化,让程序员将编程的重心回归到业务本身,以达到提高开发效率的目的。


二、为什么要控制反转?

举个例子: 创建两个类 IndexServicreIndexDAOImpl 分别作为服务层对象和数据交互层对象。当需要执行一个业务逻辑时,最先是请求调用IndexService ,而IndexService则会调用IndexDAOImpl执行对应的数据库操作。在不使用IOC思想的情况下,你会如何实现??按照正常的套路来说应该像下图所示:
手撸Spring系列1:IOC/DI 思想(理论篇)_第2张图片
转换为代码如下:
手撸Spring系列1:IOC/DI 思想(理论篇)_第3张图片
这么看来,我们已经完美的完成了上述的业务要求,但是!!! 降低代码耦合性始终是我们程序员需要去考虑的问题,在上述的代码中,如果因为某种原因,我们需要将IndexDAOImpl接口替换成一个新的接口NewIndexDAOImpl,你会怎么做?

那不简单嘛,直接将上述代码中的new IndexDAOImpl() 手动修改为 new NewIndexDAOImpl() 不就好了。

当然,这种简单暴力的方式也未尝不可,但在实际的项目中,需要调用IndexDAOImpl的服务层对象可不单单只有IndexService一个,会有十几个甚至几十个服务层对象去调用DAO层对象,如果是按照这种简单暴力的方式一个一个的修改,工作量是非常庞大的,费时费力而且没有任何的意义,只会大大的降低了程序员的开发效率(下图是多个service调用dao时,需要逐个修改接口的示意图)
手撸Spring系列1:IOC/DI 思想(理论篇)_第4张图片
如上图,当需要去改变所有service层对象的接口时,需要我们程序员自己去手动修改(这个修改的操作对应图中service的连线从IndexDAOImpl转为NewIndexDAOImpl),如果service层中的对象有一百个,这种替换操作则需要程序员重复一百次。

因此,IOC思想的重要性就凸显出来了,当我们将控制权交由第三方框架Spring去管理,出现了上述需要切换接口的要求时,去修改每个service的任务自然也由Spring去帮助我们完成,我们程序员自己无需去做这种庞大而又无意义的任务。

程序员只需要告诉Spring:“嘿!大兄弟,这个接口我们不用了,换成了一个新的接口了,接下来的修改任务都交给你了,加油!奥里给!!我先去刷会儿抖音了”而这个“告诉”的操作,其实就是修改Spring的配置文件或者修改配置注解。(以下是使用IOC思想后修改接口的示意图)
手撸Spring系列1:IOC/DI 思想(理论篇)_第5张图片
上两张图对比,可以明显的发现,使用IOC思想后,重复性的操作都交给了第三方框架了,也就是我们苦命的Spring大兄弟。反观我们程序员自身,需要亲历亲为的操作大大的减少,开发效率提高,代码之间的耦合性也大大的降低了!!


三、什么是DI(依赖注入)?

所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。

当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。

依赖注入有两种:设值注入、构造注入需要注意的是,网上还有许多的博客说Spring的DI有三种注入方式,分别是setter、构造、以及接口,但在Spring5是已经不支持接口注入的方式了,希望大家不要搞错!!

Spring5官方文档原话: DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.(依赖注入有两种主要的变体:基于构造函数的依赖注入和基于setter的依赖注入。)

DI和IOC的关系: IOC不是一种技术,是一种编程思想,与设计模式一样,它指导程序员应该如何去编写低耦合、高可用的代码。而DI则是IOC众多具体实现方案中的一个,并不是唯一的(除了DI依赖注入外,还有DL依赖查找DP依赖拖拽,但这两种实际项目中使用的并不多)


四、依赖注入中“注入”的是什么?

举个例子: 光是用文字诉说理论,对于刚接触Spring的读者伙伴来说可能有点晦涩难懂。我们就拿MyBatis来举例吧,当我们使用SpringBoot注解的方式来整合MyBatis时,一般需要以下步骤:

  • 1.配置文件中配置数据库连接相关配置
  • 2.编写表的映射类
  • 3.编写DAO层对象UserMapper接口,并使用注解 @Repository 标记
  • 4.编写具体的操作方法(如编写findAll方法查找表中的所有数据,使用注解 @Select 标记,并在其中写上具体需要执行的sql语句

完成上面的步骤后我们的SpringBoot就可以宣告整合MyBatis成功了!
手撸Spring系列1:IOC/DI 思想(理论篇)_第6张图片
此时我们只需要在对应的Server层调用该方法即可(如下,在DemoApplicationTests中存在成员变量userMapper,并且该变量由注解 @Autowired 标记)
手撸Spring系列1:IOC/DI 思想(理论篇)_第7张图片
此时,问题就来了,不知道大家一开始用这种方式整合时有没有这么一个疑惑:在初学Java的时候,相信教大家的老师都会和大家强调,接口是不能被实例化的,因此,我们是无法直接去调用接口的方法的。但是,在上述代码中,Service中却可以直接去调用userMapperfindAll()方法,这是怎么回事??

为了探究userMapper接口中的为什么可以被直接调用,我们可以在调用findAll()前打个断点debug一下,观察此时的userMapper的状态:
手撸Spring系列1:IOC/DI 思想(理论篇)_第8张图片
当程序运行到断点时,idea能够智能的帮助我们将每个变量对应的类型显示出来,此时我们发现userMapper变量对应的是一个叫做MapperProxy的类型,这又是怎么回事呢?

原来,使用 @Autowried 注解标记的成员变量,在类被调用时,会对该类下的成员变量使用Java的反射机制注入一个代理类对象,其遵循配置文件以及userMapper接口的设置并装配好了sql语句,使得程序员可以直接使用这个代理类完成对数据库的操作。(具体的注入操作将会在源码篇进行详细的讲解!!)


说点题外话

在一般情况向一个成员变量注入一个bean在其上方加上 @Autowried 即可(如下)
手撸Spring系列1:IOC/DI 思想(理论篇)_第9张图片
但这种方式在一些特殊的情况下会抛出NPE(NullPointerException)异常,这是程序执行顺序导致的,在bean没有完成实例化前开始对UserMapper执行注入,导致Spring没有找到对应bean的实例,只能注入一个null对象。

此时为了解决该问题我们需要将该成员变量改为私有静态的,为其书写一个set方法并将 @Autowried 注解加在该方法上(如下)
手撸Spring系列1:IOC/DI 思想(理论篇)_第10张图片
至此,相信各位读者朋友们已经对Spring IOC DI 的理论知识有了最初步的认识,对于Spring在底层中是如何一步步进行实现的,还请各位读者们期待我的下一篇博客!
手撸Spring系列1:IOC/DI 思想(理论篇)_第11张图片

你可能感兴趣的:(手写Spring迷你版,spring,java)