【.com原创稿件】Java 作为流行的腾讯开发语言被广大开发者所青睐,在 Java 平台提供丰富的必问应用程序开发功能的同时,其存在的腾讯问题也暴露出来。 图片来自包图网 这个问题就是必问其缺乏将基础组件构建成完整系统的能力,因此开发者需要通过各种设计模式,腾讯将开发的必问组件进行组合,从而构建成最终的腾讯应用。 为了解决这个问题,必问Spring 架构推出了 IoC 组件,腾讯它可以通过正规化的必问方法来组合不同的组件,让其成为完整的腾讯,可以用的必问应用。 从此开发人员无须手动设置对象的腾讯依赖关系,把这一工作交给了 Spring 容器去处理和管理,必问提升了开发体验。腾讯 今天将围绕 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 IoC 做的事情就是管理和创建 Bean 的实例,同时保证 Bean 之间的依赖关系。 这里我们引出 Spring IoC,IoC(Inversion of Control)也称为控制反转,也就是对象定义其依赖关系的控制反转。 原来这个过程是:谁使用谁创建,例如上面的例子中 UserController 需要使用 UserService,于是就由 UserController 创建 UserService 的实例。 引入 IoC 以后,这个创建过程发生的反转,这些 UserController 和 UserService 之间的依赖关系由 XML 文件定义以后由 Spring 容器进行创建。 这个控制权从对象的使用者转换为 Spring 容器,就成为控制反转。也就是对象之间的依赖过程发生了变化,由原来的主动创建,变成了现在被动关联(因为 Spring 容器的参与),这种控制权颠的现象被称为控制反转。 前面说了 IoC 的来历和概念,实际上它是用来管理对象初始化的容器,这里会针对 Spring IoC 容器介绍其主要功能。 Spring IoC 容器将创建对象,通过配置设定它们之间的依赖关系,并管理它们的生命周期(从创建到销毁)。 Spring IoC 容器管理的对象被称为 Spring Beans,也就是上面例子中提到的 UserController 和 UserService。 通过阅读配置文件元数据提供的指令,容器知道对哪些对象进行实例化,配置和组装。 配这里的置元数据就是上面例子的 XML,不过处理 XML 的配置之外还可以通过 Java 注释或 Java 代码来表示,大家可以理解为一种配置对象之间关系的方式。 说了这么多的 Spring IoC 容器的作用,在 Spring 中实现 IoC 容器的实际代表者是谁呢? 这里介绍两类 Spring IoC 容器的代表者,分别是: 它是最简单的容器,用 org.springframework.beans.factory.BeanFactory 接口来定义。 BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的与 Spring 整合的第三方框架的反向兼容性的目的。 添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。 该容器是由 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 所示: 在介绍过 Spring IoC 的原理和容器实现以后,相信大家对 IoC 有所了解了,不过任何技术和架构都有其优缺点 Spring IoC 也不例外在使用它之前,还需要对其有清晰的认识。 首先是优点的部分: 有优点就一定有缺点: 前面说了 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 方法的依赖注入在类中的实现 上面提到了通过构造函数和 setter 方法来注入备 bean 对象,其分别使用 XML 配置文件中的 和 <:property>元素来完成注入 。 为了减少 XML 配置的数量,Spring 容器可以在不使用 和 <:property>元素的情况下配置 bean 之间的关系, 这种注入的方式称为自动装配。 下面我们来看看几种自动装配的方式: 如果在类中定义了与其他类的依赖关系,那么 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 依赖关系,也就是依赖注入,这种方式也是基于类型的自动装载。 有了 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 定义关系 例如: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】Spring IoC 的由来和概念
Spring IoC 容器
①Spring BeanFactory 容器
②Spring ApplicationContext 容器
Spring IoC 的优缺点
IoC 与 DI
DI 的自动装载
①byType,这种方式由属性数据类型自动装配
②constructor,适用于构造函数参数类型的自动加载
③byName,通过指定特定的 bean 名称,容器根据名称自动选择 bean 属性,完成依赖注入
总结