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

【.com原创稿件】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 三种。

作者:崔皓

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

编辑:陶家龙

征稿:有投稿、寻求报道意向技术人请联络 editor@51cto.com

【原创稿件,合作站点转载请注明原文作者和出处为.com】

应用开发
上一篇:云计算采用和人工智能如何推动数据中心发展?
下一篇:企业正悄悄地将数据中心迁入城市以减少延时