乐趣区

关于java:Spring-IOC

面试被问烂的 Spring IOC(求求你别再问了)

SONG公布于 2019-08-06

狭义的 IOC

  • IoC(Inversion of Control) 管制反转,即“不必打电话过去,咱们会打给你”。两种实现:
    依赖查找(DL)和依赖注入(DI)。

IOC 和 DI、DL 的关系(这个 DL,Avalon 和 EJB 就是应用的这种形式实现的 IoC):

  • DL 曾经被摈弃,因为他须要用户本人去是应用 API 进行查找资源和组装对象。即有侵入性。
  • DI 是 Spring 应用的形式,容器负责组件的拆卸。

留神:Java 应用 DI 形式实现 IoC 的不止 Spring,包含 Google 的 Guice,还有一个冷门的 PicoContainer(极度轻量,但只提供 IoC)。

Spring 的 IoC

Spring 的 IoC 设计反对以下性能:

  • 依赖注入
  • 依赖查看
  • 主动拆卸
  • 反对汇合
  • 指定初始化办法和销毁办法
  • 反对回调某些办法(然而须要实现 Spring 接口,略有侵入)

其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象。

对于 IoC 来说,最重要的就是容器。容器治理着 Bean 的生命周期,管制着 Bean 的依赖注入。

那么,Spring 如何设计容器的呢?

Spring 作者 Rod Johnson 设计了两个接口用以示意容器。

  • BeanFactory
  • ApplicationContext

BeanFactory 粗犷简略,能够了解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个性能。咱们能够称之为“低级容器”。

ApplicationContext 能够称之为“高级容器”。因为他比 BeanFactory 多了更多的性能。他继承了多个接口。因而具备了更多的性能。例如资源的获取,反对多种音讯(例如 JSP tag 的反对),对 BeanFactory 多了工具级别的反对期待。所以你看他的名字,曾经不是 BeanFactory 之类的工厂了,而是“利用上下文”,代表着整个大容器的所有性能。该接口定义了一个 refresh 办法,此办法是所有浏览 Spring 源码的人的最相熟的办法,用于刷新整个容器,即从新加载 / 刷新所有的 bean。

当然,除了这两个大接口,还有其余的辅助接口,但我明天不会花太多篇幅介绍他们。

为了更直观的展现“低级容器”和“高级容器”的关系,我这里通过罕用的 ClassPathXmlApplicationContext 类,来展现整个容器的层级 UML 关系。

有点简单?先不要慌,我来解释一下。

最下面的 BeanFactory 晓得吧?我就不讲了。

上面的 3 个绿色的,都是性能扩大接口,这里就不开展讲。

看上面的附属 ApplicationContext 粉红色的“高级容器”,依赖着“低级容器”,这里说的是依赖,不是继承哦。他依赖着“低级容器”的 getBean 性能。而高级容器有更多的性能:反对不同的信息源头,能够拜访文件资源,反对利用事件(Observer 模式)。

通常用户看到的就是“高级容器”。但 BeanFactory 也十分够用啦!

右边灰色区域的是“低级容器”,只负载加载 Bean,获取 Bean。容器其余的高级性能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置。生命周期事件回调等。

好,解释了低级容器和高级容器,咱们能够看看一个 IoC 启动过程是什么样子的。说白了,就是 ClassPathXmlApplicationContext 这个类,在启动时,都做了啥。(因为我这是 interface21 的代码,必定和你的 Spring 4.x 系列不同)。

下图是 ClassPathXmlApplicationContext 的结构过程,理论就是 Spring IoC 的初始化过程。

留神,这里为了了解不便,有所简化。

这里再用文字来形容这个过程:

  1. 用户结构 ClassPathXmlApplicationContext(简称 CPAC)
  2. CPAC 首先拜访了“形象高级容器”的 final 的 refresh 办法,这个办法是模板办法。所以要回调子类(低级容器)的
    refreshBeanFactory 办法,这个办法的作用是应用低级容器加载所有 BeanDefinition 和 Properties
    到容器中。
  3. 低级容器加载胜利后,高级容器开始解决一些回调,例如 Bean 后置处理器。回调 setBeanFactory
    办法。或者注册监听器等,公布事件,实例化单例 Bean 等等性能,这些性能,随着 Spring
    的一直降级,性能越来越多,很多人在这里迷失了方向:)。

简略说就是:

  1. 低级容器 加载配置文件(从 XML,数据库,Applet),并解析成 BeanDefinition 到低级容器中。
  2. 加载胜利后,高级容器启动高级性能,例如接口回调,监听器,主动实例化单例,公布事件等等性能。

所以,肯定要把“低级容器”和“高级容器”的区别弄清楚。不能一叶障目不见泰山。

好,当咱们创立好容器,就会应用 getBean 办法,获取 Bean,而 getBean 的流程如下:

从图中能够看出,getBean 的操作都是在低级容器里操作的。其中有个递归操作,这个是什么意思呢?

假如:当 Bean_A 依赖着 Bean_B,而这个 Bean_A 在加载的时候,其配置的 ref =“Bean_B”在解析的时候只是一个占位符,被放入了 Bean_A 的属性汇合中,当调用 getBean 时,须要真正 Bean_B 注入到 Bean_A 外部时,就须要从容器中获取这个 Bean_B,因而产生了递归。

为什么不是在加载的时候,就间接注入呢?因为加载的程序不同,很可能 Bean_A 依赖的 Bean_B 还没有加载好,也就无奈从容器中获取,你不能要求用户把 Bean 的加载顺序排列好,这是不人道的。

所以,Spring 将其分为了 2 个步骤:

  1. 加载所有的 Bean 配置成 BeanDefinition 到容器中,如果 Bean 有依赖关系,则应用占位符临时代替。
  2. 而后,在调用 getBean 的时候,进行真正的依赖注入,即如果碰到了属性是 ref 的(占位符),那么就从容器里获取这个
    Bean,而后注入到实例中 —— 称之为依赖注入。

能够看到,依赖注入实际上,只须要“低级容器”就能够实现。

这就是 IoC。

所以 ApplicationContext refresh 办法外面的操作不只是 IoC,是高级容器的所有性能(包含 IoC),IoC 的性能在低级容器里就能够实现。

总结

说了这么多,不晓得你有没有了解 Spring IoC?这里小结一下:IoC 在 Spring 里,只须要低级容器就能够实现,2 个步骤:

a. 加载配置文件,解析成 BeanDefinition 放在 Map 里。

b. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 办法 —— 实现依赖注入。

下面就是 Spring 低级容器(BeanFactory)的 IoC。

至于高级容器 ApplicationContext,他蕴含了低级容器的性能,当他执行 refresh 模板办法的时候,将刷新整个容器的 Bean。同时其作为高级容器,蕴含了太多的性能。一句话,他不仅仅是 IoC。他反对不同信息源头,反对 BeanFactory 工具类,反对层级容器,反对拜访文件资源,反对事件公布告诉,反对接口回调等等。

能够预感,随着 Spring 的一直倒退,高级容器的性能会越来越多。

诚然,理解 IoC 的过程,实际上为了理解 Spring 初始化时,各个接口的回调机会。例如 InitializingBean,BeanFactoryAware,ApplicationListener 等等接口,这些接口的作用,笔者之前写过一篇文章进行介绍,有趣味能够看一下,关键字:Spring 必知必会 扩大接口。

然而请留神,实现 Spring 接口代表着你这个利用就绑定死 Spring 了!代表 Spring 具备侵入性!要晓得,Spring 公布时,无侵入性就是他最大的宣传点之一 —— 即 IoC 容器能够轻易更换,代码无需变动。而现如今,Spring 未然成为 J2EE 社区准官网解决方案,也没有了所谓的侵入性这个说法。因为他就是规范,和 Servlet 一样,你能不实现 Servlet 的接口吗?: -)

好了,下次如果再有面试官问 Spring IoC 初始化过程,就再也不会含糊其词、支支吾吾了。

javaspring

退出移动版