Spring 学习整理 -02- Spring 之 IoC 容器理解

版权声明:本文为博主原创文章,欢迎转载,转载请注明出处。觉得此文有用的,不嫌麻烦的,就留个言呐,或者点个赞呐,要是嫌麻烦呢,也麻烦点个赞嘛 https://blog.csdn.net/qq_40147863/article/details/87859568

Spring 学习整理 -02- Spring 之 IoC 容器理解

为面试做准备

参考原文:https://blog.csdn.net/MobiusStrip/article/details/87856628

Ioc 控制反转

Ioc(Inversion of control) 控制反转,这里的控制指把控制权从应用程序中剥离出来。ioc 它可以把创建对象和查找依赖对象的权限交给 Ioc 容器控制,而不是传统的由这些对象的使用方(消费者)进行创建初始化操作。Ioc 是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。

为什么 Ioc 叫控制反转呢,反转了什么呢?传统的程序都是消费者主动创建对象,现在容器帮我们查找及注入依赖对象,而消费者只是被动的接受依赖对象,此为反转。

DI 依赖注入

DI(Dependency Injection)依赖注入,指容器复制创建和维护对象之间的依赖关系,而不是通过对象本身复制自己的创建和解决自己的依赖。控制反转是通过依赖注入实现的。

其实 Ioc 和 DI 在 Spring 中是一个等同的概念。如果非要咬文嚼字的话,控制反转是依赖注入的一部分,或者说是同一个行为偏重点不同的俩个称呼。

他们是从不能的角度阐述同一个功能,描述的对象不同而已。依赖注入是从程序本身来说,控制反转是从容器来说的。

其实 IoC 包括依赖查找(DL)和依赖注入(DI);只不过 DL 因为有侵入性 (它需要用户自己去是使用 API 进行查找资源和组装对象),已经被抛弃。所以现在提到 IoC,更多的想到的就是依赖注入(DI)了。

依赖注入(DI)包括Set注入和构造器注入!其实还有一个通过实现接口的方式实现依赖注入,不过不常用,就不说了。

IOC 和 DI 只不过是看待问题的角度不同而已:

IOC: Spring 反向控制应用程序需要的资源。

DI: 应用程序依赖 Spring 为其提供资源。

IOC 是站在 Spring 的角度,而 DI 是站在应用程序的角度。

接下来详细介绍一下 IoC,从其初始化到实现过程,细细理解!

IoC 粗理解

IoC 亦称为“依赖倒置原理”(Dependency Inversion Principle),几乎所有框架都使用了倒置注入(Martin Fowler)技巧,是IoC原理的一项应用。SmaIITaIk、C++、Java和.NET面向对象语言的程序员已使用了这些原理。但是Spring是Java语言实现中最著名的一个。同时,控制反转即是Spring框架的核心,也是Spring框架要解决的核心问题。

IoC 细理解

由于很多对象的依赖关系和维护并不需要和系统运行状态有很强的关联性,所以可以把在面向对象编程中需要执行的诸如新建对象、为对象引用赋值等操作交由容器统一完成。

这样一来,这些散落在不同代码中的功能相同的部分就集中成为容器的一部分,也就是面向对象系统的基础设施的一部分。同时,这些对象之间的相互依赖关系也是比较稳定的,一般不会随着应用的运行状态的改变而改变。

此时,IoC 容器控制了对象,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象这是正转,因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;这就解释了控制反转。

基于以上特性,这些对象使用IoC容器来管理,简直就是天作之合。虽然这些特性存在于应用系统中,但是应用系统并不承担管理这些对象的责任,而是通过依赖反转把责任交给了容器(也可以说是平台)。

有了以上这些基础知识储备,Spring IoC 容器的原理也就不难理解了。

Spring 中 IoC 的应用

在 Spring 中,Spring IoC 提供了一个基本的 JavaBean 容器,通过 IoC 模式管理依赖关系,并通过依赖注入和 AOP 切面增强了为 JavaBean 这样的 POJO 对象赋予事务管理、生命周期管理等基本功能。

IoC 容器

容器的两种表现形式

Spring 作者 Rod Johnson设计了两个接口用以表示容器:
BeanFactory 和 ApplicationContext

BeanFactory 粗暴简单,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。

ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。

故我们可以认为直接的 BeanFactory 实现是 IoC 容器的基本形式,而各种ApplicationContext 的实现是IoC容器的高级表现形式。所以亦可简单的把 Spring IoC 通过BeanFactory的实现当做低级容器。把 ApplicationContext 的实现当做高级容器。

此文我们主要讲解 Spring 低级容器(BeanFactory)的 IoC;因为高级容器 ApplicationContext,它包含了低级容器的功能,当它执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,它包含了太多的功能,不仅仅是 IoC。它支持不同信息源头,支持 BeanFactory 工具类、支持层级容器、支持访问文件资源、支持事件发布通知、支持接口回调等等。

BeanFactory 的 IoC 实现过程:

IoC 在 Spring 里,只需要低级容器(BeanFactory)就可以实现,两个步骤:

1、加载配置文件,解析成 BeanDefinition 放在 Map 里。
2、调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。getBean 的流程如下所示:

IoC容器初始化过程

值得注意的是,在这个过程中,一般不包含 Bean 侬赖注入的实现。

在 Spring IoC 的设计中,Bean 定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过 getBean 向容器索取 Bean 的时候。

但有一个例外值得注意,在使用 loc 容器时有一个预实例化的配置,通过这个预实例化的配置(具体来说,可以通过为 Bean 定义信息中的 lazyinit 属性),用户可以对容器初始化过程作一个微小的控制,从而改变这个被设置了 lazyinit 属性的 Bean 的依赖注入过程。

举例来说,如果我们对某个 Bean 设置了 lazyinit 属性,那么这个 Bean 的依赖注入在 IoC 容器初始化时就预先完成了,而不需要等到整个初始化完成以后,第一次使用 getBean 时才会触发。

BeanDefinition 的定位

对 loC 容器来说,它为管理 POJO 之间的依赖关系提供了帮助,但也要依据Spring的定义规则提供 Bean 定义信息。我们可以使用各种形式的Bean定义信息,其中比较熟悉和常用的是使用 XML 的文件格式。

在 Bean 定义方面,Spring 为用户提供了很大的灵活性。在初始化 IoC 容器的过程中,首先需要定位到这些有效的 Bean 定义信息,这里 Spring 使用 Resource 接口来统一这些 Bean 定义信息,而这个定位由 ResourceLoader 来完成。

如果使用上下文,ApplicationContext 本身就为客户提供了定位的功能。因为上下文本身就是 DefaultResourceLoader 的子类。
如果使用基本的 BeanFactory 作为 loC 容器,客户需要做的额外工作就是为 BeanFactory指定相应的 Resource 来完成 Bean 信息的定位。

BeanDefinition 的载入

信息的载入过程。对 IoC 容器来说,这个载入过程,相当于把定义的 BeanDefinition 在IoC 容器中转化成一个 spring 内部表示的数据结构的过程。IoC 容器对 Bean 的管理和依赖注入功能的实现,都是通过对其持有的 BeanDefinition 进行各种相关操作来完成的。这些BeanDefinition 数据在 IoC 容器中通过一个 HashMap 来保持和维护。当然这只是一种比较简单的维护方式,如果需要提高 IoC 容器的性能和容量,完全可以自己做一些扩展。

IoC 容器的依赖注入

IoC 容器的初始化过程完成的主要工作是在 IoC 容器中建立 BeanDefinition 数据映射。但在此过程中并没有 IoC 容器对 Bean 依赖关系进行注入,那么 IoC 容器是怎样对 Bean 的依赖关系进行注入的呢?

假设当前 IoC 容器已经载入了用户定义的 Bean 信息,开始分析依赖注入的原理:

首先,依赖注入的过程是用户第一次向容器索要 Bean 时触发的,当然也有例外,也就是我们可以在 BeanDefinition 信息中通过控制 lazy-init 属性来让容器完成对 Bean 的预实例化。这个预实例化实际上也是一个完成依赖注入的过程,但它是在初始化的过程中完成的。

所以,当用户向 IoC容器索要 Bean 时,如果读者还有印象,那么一定还记得在基本的 IoC 容器接口 BeanFactory 中,有一个 getBean 的接口定义,这个接口的实现就是触发依赖注入发生的地方(也就是依赖注入的入口);而依赖注入的发生是在容器中的 BeanDefinition 数据已经建立好的前提下才能完成的。

IoC 小结

尽管可以用最简单的方式来描述 IoC 容器,将它视为一个 hashMap,但只能说这个 hashMap 是容器的最基本的数据结构,而不是IoC容器的全部。

打个比方来讲,使用 IoC 后相当于 IoC 就是一个饮品店;以前的我们需要自己 new 对象,也就是需要自己买橙子,买榨汁机来榨果汁喝;而是用 IoC 后,我们只需要把需求(想喝橙汁)告诉它,然后由它给我们提供橙汁就可以了。这样子想,是不是 IoC 就感觉简单多了呢?

Spring IoC 容器作为一个产品,其价值体现在一系列相关的产品特性上,这些产品特性以依赖反转模式的实现为核心,为用户更好地使用依赖反转提供便利,从而实现了一个完整的 IoC 容器产品。

更多文章链接

猜你喜欢

转载自blog.csdn.net/qq_40147863/article/details/87859568