腾讯必问的Spring IOC,要看看了

编程语言 0 1282
新智AI
新智AI 2021年7月17日 10:17 发表
摘要: Java 作为流行的开发语言被广大开发者所青睐,在 Java 平台提供丰富的应用程序开发功能的同时,其存在的问题也暴露出来。

Java 作为流行的开发语言被广大开发者所青睐,在 Java 平台提供丰富的应用程序开发功能的同时,其存在的问题也暴露出来。

图片来自包图网

这个问题就是其缺乏将基础组件构建成完整系统的能力,因此开发者需要通过各种设计模式,将开发的组件进行组合,从而构建成最终的应用。

为了解决这个问题,Spring 架构推出了 IoC 组件,它可以通过正规化的方法来组合不同的组件,让其成为完整的,可以用的应用。

从此开发人员无须手动设置对象的依赖关系,把这一工作交给了 Spring 容器去处理和管理,提升了开发体验。

今天将围绕 Spring IoC 给大家讲解其实现原理,接下来将会学到如下内容:

  • Spring IoC 的由来和概念
  • Spring IoC 容器
  • Spring IoC 的优缺点
  • IoC 与 DI
  • DI 的自动装载

Spring IoC 的由来和概念

在介绍 Spring IoC 之前先来看看传统的对象(组件)依赖是怎么做的,假设通过 RESTFUL 的方式访问用户信息(User)。

如图 1 所示,用户请求一个 UserController 获取 User 信息,UserController 会调用 UserService,在 UserService 中会处理关于 User 的业务逻辑。

图 1:例子依赖关系

同时 UserService 会调用 UserDao,UserDao 负责调用数据库返回用户需要的信息。

从这张可以看出 UserController 依赖 UserService、UserService 依赖 UserDao。

如图 2 所示,假设在 UserController 中需要使用 UserService,就需要在其 UserController 构造函数中对 UserService 进行实例化。

图 2:传统的依赖关系需要自己管理对象实例化

这样才能 save 方法中使用 UserService,并且调用其 save 方法。

与传统的依赖方式不同,Spring IoC 会通过一个 XML 文件配置对象之间的关系。

如图 3 所示,在 beans 的标签中,定义了两个 bean,分别是 UserController 和 UserService。在 Class 属性中定义了 Class 的全程(包含 Namespace)。

图 3:Spring IoC 的依赖关系 XML 配置

需要注意的是在 UserController的bean 定义中指定了 contructor-arg 的 ref 为 UserService。

这里的含义是在 UserController 的构造函数中会引入 UserService,从而说明两者之间的依赖关系,也就是 UserController 会依赖 UserService。

看完了 XML 的配置再回头看看代码中有什么改变,如图 4 所示,在 UserController 的构造函数的初始化参数中加入 UserService 作为依赖项。

图 4:Spring IoC 代码中的改变

不过 New UserService 的动作就不再 UserController 中完成了,而是由 Spring 容器完成。

Spring 容器完成 UserService 的初始化之后,在 UserController 需要使用的时候直接使用这个 UserService 实体就行了。

图 5:Spring IoC 的 Spring 容器

这里再将 Spring IoC 做的事情梳理一下,如图 5 所示:

  • 位于中间的 Spring 容器会读取 XML 配置文件中的信息,获取 Bean 之间的依赖关系。
  • Spring 容器通过反射机制创建对象的实例,由于 Spring 容器管理所有注册 Bean 因此为后续建立它们之间的依赖关系打下基础。
  • Spring 容器通过 Bean 之间的依赖关系创建实例,同时保证 Bean 在使用依赖项的时候直接过去对应的实例,而不用自己去创建实例。

说白了 Spring IoC 做的事情就是管理和创建 Bean 的实例,同时保证 Bean 之间的依赖关系。

这里我们引出 Spring IoC,IoC(Inversion of Control)也称为控制反转,也就是对象定义其依赖关系的控制反转。

原来这个过程是:谁使用谁创建,例如上面的例子中 UserController 需要使用 UserService,于是就由 UserController 创建 UserService 的实例。

引入 IoC 以后,这个创建过程发生的反转,这些 UserController 和 UserService 之间的依赖关系由 XML 文件定义以后由 Spring 容器进行创建。

这个控制权从对象的使用者转换为 Spring 容器,就成为控制反转。也就是对象之间的依赖过程发生了变化,由原来的主动创建,变成了现在被动关联(因为 Spring 容器的参与),这种控制权颠的现象被称为控制反转。

Spring IoC 容器

前面说了 IoC 的来历和概念,实际上它是用来管理对象初始化的容器,这里会针对 Spring IoC 容器介绍其主要功能。

Spring IoC 容器将创建对象,通过配置设定它们之间的依赖关系,并管理它们的生命周期(从创建到销毁)。

Spring IoC 容器管理的对象被称为 Spring Beans,也就是上面例子中提到的 UserController 和 UserService。

通过阅读配置文件元数据提供的指令,容器知道对哪些对象进行实例化,配置和组装。

配这里的置元数据就是上面例子的 XML,不过处理 XML 的配置之外还可以通过 Java 注释或 Java 代码来表示,大家可以理解为一种配置对象之间关系的方式。

说了这么多的 Spring IoC 容器的作用,在 Spring 中实现 IoC 容器的实际代表者是谁呢?

这里介绍两类 Spring IoC 容器的代表者,分别是:

①Spring BeanFactory 容器

它是最简单的容器,用 org.springframework.beans.factory.BeanFactory 接口来定义。

BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的与 Spring 整合的第三方框架的反向兼容性的目的。

②Spring ApplicationContext 容器

添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。

该容器是由 org.springframework.context.ApplicationContext 接口定义。

由于 ApplicationContext 容器包括 BeanFactory 容器的所有功能,同时 BeanFactory 适用于轻量级应用。

这里我们将目光放到 ApplicationContext 容器上,看看它是如何实现 Spring IoC 容器的功能的。

由于 ApplicationContext 是一个接口,针对它有几种不同的实现,这些实现会针对不同使用场景,以下列出三种不同实现:

FileSystemXmlApplicationContext:实现了从 XML 文件中加载 bean。初始化该类的时候需要提供 XML 文件的完整路径。

ClassPathXmlApplicationContext:也实现了 XML 文件中加载 bean,与上面一种方式不同的是:不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,容器会从 CLASSPATH 中搜索 bean 配置文件。

WebXmlApplicationContext:实现了在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

由于篇幅原因,这里我们针对 FileSystemXmlApplicationContext 实现 Spring IoC 容器进行说明。

图 6:FileSystemXmlApplicationContext 实现 Spring IoC 容器

如图 6 所示:

  • 在使用 FileSystemXmlApplicationContext 实现类之前需要引入相关的包,由于其是接口 ApplicationContext 的实现类,因此需要引入 ApplicationContext 的包,以及自身 FileSystemXmlApplicationContext 的包。
  • 在进行 FileSystemXmlApplicationContext 实例化时传入 XML 文件的地址,也就是上文中配置 bean 对象的 XML 文件地址,这里是“C:/Test/src/Beans.xml”。
  • 最后通过 FileSystemXmlApplicationContext 所带的 getBean 方法,通过传入 bean id 的方式获取 bean 对象的实例,这里传入“userController”,从而调用 userController 中的 save 方法完成业务。

Spring IoC 的优缺点

在介绍过 Spring IoC 的原理和容器实现以后,相信大家对 IoC 有所了解了,不过任何技术和架构都有其优缺点 Spring IoC 也不例外在使用它之前,还需要对其有清晰的认识。

首先是优点的部分:

  • 灵活性,由于类之间依赖可以灵活配置,因此可以设置类对于接口的依赖,在针对变现对应的实现类,这种方式让依赖接口的实现类更加方便,提倡面向接口编程,提高程序的可扩展性。
  • 可读性,每个bean之间的依赖关系清晰,由于 Spring IoC 容器来管理 bean 的实例,因此不需要创建一堆工厂类来生成不同的 bean。
  • 可测性,由于通过 IoC 的方式让每个 bean 都解耦了,可以针对单独的 bean 进行测试,而且 bean 之间的依赖关系也很明确,如果想替换其中的 bean 进行测试也是很容易的事情。

有优点就一定有缺点:

  • 复杂,由于引入 IoC 容器,对象生成步骤变得复杂,本来哪里使用哪里生成对象的,现在凭空多出 XML 配置依赖项之间的关系,让系统调用变得不太直观。因此会增加团队学习成本,需要团队提升这方面的技能。
  • 性能,IoC 容器生成对象是通过反射方式,在运行效率上有一定的损耗,它允程序在运行时(不是编译时)对成员进行操作。
  • 配置,IoC 框架需要进行大量的配制工作,无形中会增加开发成本。

IoC 与 DI

前面说了 IoC 及控制反转,一般来说和 IoC 一同出现的有 DI(Dependency Injection)也就是依赖注入,这两个概念之间有什么关系呢?

在 2004 年 Martin Fowler 在探索 IOC 控制反转问题的时候,提出:“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。

控制被反转之后,获得依赖对象的过程由自身管理变为了由 IOC 容器主动注入。

于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。

他的这个答案,实际上给出了实现 IOC 的方法:注入。所谓依赖注入,就是由 IoC 容器在运行期间,动态地将某种依赖关系注入到对象之中。

就好像上面提到的例子一样,将 UserService 注入到 UserController 一样,这个过程是由 Spring IoC 容器来完成的。

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

基于对 IoC 和 DI 两个概念的理解,再来看看实现 DI 的两种方式:基于构造函数的 DI 和基于 setter 方法的 DI。

基于构造函数的 DI,在上面的例子中提到过这里进行一下回顾,如图 7 所示,在 XML 的配置文件中定义 UserController 的 bean 同时将 ref 指定 UserService,也就是需要注入的 bean。

图 7:构造函数 DI 的配置文件

需要注意的是,这里通过设置 contructor-arg 指定构造函数注入方式。

如图 8 所示,在类文件中 UserController 的构造函数中传入的参数就是 UserService。

Spring IoC 容器在完成依赖注入和对象初始化以后,在 UserController 中之间使用对象的实例展开后续的业务操作。

图 8:UserController 使用依赖注入和被初始化以后的 UserService 对象

如图 9 所示,在配置 bean 节点中稍微做了调整,将 contructor-arg 修改为了 property,通过 property 属性定义与 UserService 的 setter 方法的依赖关系。

图 9:UserController 定义 setter 方法的依赖关系

再来看看类中的修改,如图 10 所示,与构造函数注入方式不同的是,在 UserController 中加入了一个 setUserService 的方法来设置 UserService 的属性,传入的参数依旧是 UserService。

图 10:setter 方法的依赖注入在类中的实现

DI 的自动装载

上面提到了通过构造函数和 setter 方法来注入备 bean 对象,其分别使用 XML 配置文件中的 和 <:property>元素来完成注入 。

为了减少 XML 配置的数量,Spring 容器可以在不使用 和 <:property>元素的情况下配置 bean 之间的关系, 这种注入的方式称为自动装配。

下面我们来看看几种自动装配的方式:

①byType,这种方式由属性数据类型自动装配

如果在类中定义了与其他类的依赖关系,那么 Spring 容器在 XML 配置文件中会通过类型寻找对应依赖关系的 bean,然后与之关联。这个过程容器会尝试匹配和连接属性的类型。

例如 bean A 定义了 X 类型的属性, Spring 会在 ApplicationContext 中寻找一个类型为 X 的 bean,并将其注入 bean A。

如果还是觉得抽象,我们看下面的例子,如图 11 所示,UserController 设置 UserService 属性时定义了与 UserService 的依赖关系。

图 11:定义 UserController 与 UserService 的依赖关系

如图 12 所示,在 XML 配置文件中 UserController 就不需要使用 property 属性定义与 UserService 之间的关系,取而代之的是使用 autowire=“byType” 的方法。

图 12:通过 byType 定义关系

容器通过类中 setUserService 传入的 UserService 类型自动在配置文件中寻找 UserService 对应的类型,从而完成 UserController 和 UserService 依赖关系,也就是依赖注入,这种方式也是基于类型的自动装载。

②constructor,适用于构造函数参数类型的自动加载

有了 byType 的基础这个很好理解,例如 bean A 的构造函数接受 X 类型的参数,容器会在 XML 寻找 X 类型的 bean,并将其注入到 bean A 的构造函数中。

如图 13 所示,UserController 在构造函数中定义 UserService 作为初始化参数,确定了 UserController 对 UserService 的依赖。

图 13:UserController 在构造函数中定义 UserService 作为初始化参数

如图 14 所示,在 XML 配置文件中 UserController 只需要设置 autowire=“constructor”。

告诉容器通过 UserController 类中的构造方法将 UserService 注入到 UserController 中,完成 UserController 和 UserService 依赖关系,这种方式也是基于构造器的自动装载。

图 14:通过 constructor 定义关系

③byName,通过指定特定的 bean 名称,容器根据名称自动选择 bean 属性,完成依赖注入

例如:bean A 定义了一个名为 X 的属性,容器会在 XML 寻找一个名为 X 的 bean,将其注入到 bean A 中。

如图 15 所示,UserController 中定义了一个名为 myUserService 的成员属性,其类型是 UserService。

图 15:UserController 中定义了一个名为 myUserService 的成员属性

如图 16 所示,在 XML 的配置中 UserController 的 autowire 配置了“byName”。

此时容器会根据类中定义的 myUserService 成员属性(变量)自动关联到 UserService,在 UserController 中 setUserService 时自动装载 UserService 的实例。

图 16:XML 文件中 byName 的定义

总结

本文从 Spring IoC 的由来说起,通过一个简单的对象依赖例子解释了 Spring IoC 解决的问题。

它将对象的依赖关系从对象内部转移到了 IoC 容器中完成,由容器来关系对象的注册和依赖关系。

说起 Spring IoC 容器,由 BeanFactory 和 ApplicationContext 接口完成具体工作。

针对常用的 ApplicationContext 接口的三个实现类,分别实现了根据 XML 加载实例、根据 CLASSPATH 加载实例和根据 Web 应用程序范围加载实例。

在分析完 IoC 的优缺点以后,解释了 IoC 与 DI 之间的关系,DI 从另外一个角度解释了 IoC,它是在 IoC 容器运行期间动态地将依赖关系注入到对象中。

常见的依赖注入方式有:构造函数注入和 setter 方法注入。同时也给大家介绍了 DI 的自动注入(加载),其内容包括 byType、constructor 和 byName 三种。

作者:崔皓

简介:十六年开发和架构经验,曾担任过惠普武汉交付中心技术专家,需求分析师,项目经理,后在创业公司担任技术/产品经理。善于学习,乐于分享。目前专注于技术架构与研发管理。

点赞 1 收藏(1)    分享
相关标签: Spring java
问题没解决?让AI助手帮你作答 AI助手
0 个评论
  • 消灭零评论