4

零、前言

从第一天开始学习SpringBoot,到现在已经有两年了,两年以来我的搬砖能力不断增强,但理论层面仍然是零基础小白。
因此,打破瓶颈的方法,就是像学长们一样,多读书、多看理论知识,然后应用实践。

所以我开始学习Spring的两大特性之一——控制反转(IoC)

在此之前,我们需要了解什么是依赖关系
如果A的成功运行必须需要调用B,此时就可以称为B是A的依赖
image.png
举个例子,Controller要想调用Service的方法,就必须有一个可以操作的Service对象的引用(指针),此时这个Controller就依赖这个Service。
image.png

一、常规情况下,依赖关系的对象是怎么实例化的

最传统的方式就是:直接new对象,用谁就new谁。
例如:
如果想完成一个学生管理的功能,需要StudentController(控制器)、StudentService(服务)以及StudentRepository(仓库),而学生和班级是ManyToOne,因此可能会调用KlassService(班级服务)和KlassRepository(班级仓库)
而在实现班级管理时,也是类似的依赖关系。
如图所示:
image.png
如果学生Service想查看学生信息,就要:

//  实例化学生仓库
private StudentRepository studentRepository = new StudentRepository();
// 实例化班级服务和班级仓库,从而实现关联查询
private KlassService klassService = new KlassService();
private KlassRepository klassRepository = KlassRepository();

并且,如果班级Service想查看班级信息,就要:

//  实例化班级仓库
private KlassRepository klassRepository = KlassRepository();
// 实例化学生服务和学生仓库,从而查看每个班级里的学生
StudentService studentService = new StudentService();
private StudentRepository studentRepository = new StudentRepository();

此外,C层调用Service时,也需要实例化它:

StudentService studentService = new StudentService();

这样一来,确实可以通过new对象实现组件间依赖关系,但问题也很明显。

局限性

从业务逻辑上看,控制器、服务、仓库,都是负责数据流的处理和传递,所以应该都是单例模式,重复的实例化这些对象除了消耗多余资源以外,更重要的是会干扰内部分变量的正常调用。
所以在某些场合,完全可以让全局共享同一个依赖关系的实例对象。

有人可能会说,那可以继续改进一下,只在某个特定的组件中完成它的依赖关系调用,其他的组件共享这些对象。
但由于业务逻辑的不确定性,编写时很难确定组件的生命周期,
谁来创建、何时创建、何时释放、释放时能否保证它已经不被调用了,这些都是问题。

此外还有一个问题,实际创建对象的时候并不是简单的new一下,而应该通过构造函数注入一些属性。
但如果两个类相互依赖的时候,这样的对象就new不出来了(先有鸡先有蛋的问题)。

二、控制反转(Inversion of Control)

控制反转的字面意思是:本来维护对象的工作由开发者完成,所谓反转,就是把这个过程交给程序自身完成。
开发者只需要告诉Spring对象之间的依赖关系即可,实现细节由Spring通过反射的方式自动完成。
人为规定,我们把通过Spring IoC创建的组件叫做Bean。
image.png

下面有三种依赖注入的方式:

① set方法注入

例如StudentController中注入StudentService

// 第一步,在类中声明依赖关系
StudentService studentService;

// 第二步,在类中声明一个set方法
public void setStudentService(StudentService studentService) {
    this.studentService= studentService;
}

// 第三步,在xml中”告诉“Spring如何依赖
<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="StudentController" class="test.lyx6666.controller.StudentController">
        <property name="studentService" ref="studentService" />
    </bean>

    <bean id="studentService" class="test.lyx6666.controller.studentService" />
</beans>

但这种方式还是比较麻烦,笔者才疏学浅,没动手写过,只在教程里见过。

② 构造函数注入

// 第一步,声明这个类是Spring组件,例如@RestController或@Service等
@RestController
@RequestMapping("student")
public class StudentController {
    ...
}

// 第二步,声明依赖关系的变量,必须为  private final 
private final StudentService studentService;

// 第三步,在构造函数中声明想要注入的变量
public StudentController(StudentService studentService) {
  this.studentService = studentService;
}

③ @Autowired注入

// 第一步,声明这个类是Spring组件,例如@RestController或@Service等
@RestController
@RequestMapping("student")
public class StudentController {
    ...
}

// 第二步,声明依赖关系的变量
StudentService studentService;

// 第三步,在依赖关系上加入@Autowired注解
@Autowired
StudentService studentService;

简单来说,Spring为我们准备了一个容器,来放置这些依赖。
所谓容器,就是一个容纳东西的物品,现实中的容器包括水杯、水壶、冰箱、整理箱等等。
image.png

Spring容器给这些对象提供了空间,在需要装配的对象的时候,Spring会按照依赖关系,按顺序实例化这些对象,并且放置到容器中。
image.png
这些容器中的对象,将会由Spring维护,并且遵循单例模式,也就是说,全局只有一个此类的实例化对象。
当其他对象也依赖容器里已有的对象时,会直接提供容器内的引用(指针)。

三、总结

总之,IoC使对象的调用与对象关系的维护分离开来,用户在使用对象时,不必在手动实例化、手动装配,也不再需要关注它的生命周期,只需要把精力集中在对象功能上即可。
此外,IoC在需要时通过单例模式,避免了冗余的对象,降低资源消耗,也让Beans可以共享,让软件执行的逻辑更加符合业务逻辑。
事实上,使用或不使用单例模式,还是根据具体业务需求来调整。

后记

曾经在考研之前离队过一段时间,那时除了要复习以外,还有一个感觉就是,自己遇到了瓶颈,当时觉得,代码能看懂,但是记不住,浅层的代码语法和原理可以理解,但仅限于此,对于深层次的知识,不仅仅是不懂,更重要的是也不知道怎么进一步的学习,所以水平长期停留在半瓶醋的状态。
或许,这次突然意识到”学习理论是一门必修课“之后,开始有意识的去主动学习的时候,这个瓶颈期就度过了,至少心态平稳了许多,希望以后会有更多的进步吧。

这篇文章怎么来的

准确的说,是我先学习廖雪峰老师的教程,然后结合自己的理解重新默写了一遍,目的只是为了加深理解。
所以一定会有重复的内容,并非抄袭。

参考资料:

https://www.liaoxuefeng.com/w...


LYX6666
1.6k 声望75 粉丝

一个正在茁壮成长的零基础小白