Spring IoC实现原理(附思维导图)

本文适合初学者,也适合老手巩固以前的知识,针对IOC的知识点进行详细的讲解。

在刚开始接触IOC的时候,很多初学者回听到很多名词 IOC、DI、Spring IOC 等,往往听的一头雾水。所以在学习之前我们先梳理下这些概念,之后再去深入学习 IoC 的实现原理部分。

很多人都知道 IOC,都知道 IOC 是 Spring 容器的内核。

AOP、申明式事务等功能在此基础上开发的。甚至在一提到 IOC 这个概念的时候就很自然的想到 Spring 容器,并将它们混为一谈,认为 IoC 就是 Spring IoC。这样理解虽然并不妨碍我们使用 Spring 框架,但是两者却不是一回事。

IOC的概念和思想

IOC (Inversion Of Control 译为 反转控制),顾名思义,其一是控制,其二是反转。即某一个接口具体实现类的选择控制权从调用类中移除,转交给第三方决定,在 Spring 容器中是由 Bean 配置来进行控制的。

这段话比较绕口,简单来说就是有了 IoC 之后你需要什么对象,IoC 容器帮你解决,你在家等着,IoC 容器主动送货上门。没有 IoC 之前是自己亲手去创建管理对象,现在直接现成的送过来。

再回头看看,IoC 的 控制 和 反转 这个两个名词就很好理解了:

再未使用 IoC 之前,需要使用一个对象需要直接 new 一个。那么你所依赖的对象就需要你自己去控制。但是有了 IoC 之后,直接由 IoC 容器来控制依赖的对象,当你依赖一个对象的时候就由 IoC 容器创建后注入到被注入的对象中。

  • 控制表示对对象的控制
  • 反转表示依赖对象的获取反转了

而实现 IOC 概念的容器除了 Spring 还有 Guice,Jboss 等。

所以 IOC 和 Spring 中的 IOC 不能混为一谈。可以说,IOC 本身只是一种概念和设计思想,Spring 容器实现了 IOC 思想,是 IOC 的实践。

但由于 IOC 这种命名确实不够开门见山,因此业界曾进行了广泛的讨论,最终软件界的泰斗级人物 Martin Fowler 提出了 DI(Dependency Injection,依赖注入)的概念用来替代 IoC。即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除掉用类对某一接口实现类的依赖。

可以看出 DI 依赖注入的解释 比 IoC 反转控制 更直接明了,初学者更容易理解。

很多人将 IoC 和 DI 混为一谈,这样虽然也不影响使用,但至少要知道它由 IoC 变为 DI 的发展过程。

Spring IoC 的原理

Spring IoC 的底层实现是基于反射技术,不了解这块知识的同学可以看下这两篇文章学习一下:《反射一开,谁都不爱》《工厂模式的三种实现,就这么简单!》

前面说了 IoC 容器是一个大的工厂来管理所有对象和它们的依赖关系,Spring 再处理这些对象和依赖关系也很简单:

  • 使用反射技术获取对象的信息包括:类信息、成员、方法等等
  • 再通过 xml 配置 或者 注解 的方式,说明依赖关系
  • 在调用类需要使用其他类的时候,不再通过调用类自己实现,而是通过 IoC 容器进行注入。

那 Spring 究竟是如何知道哪些对象是需要管理的呢?如何进行管理的呢?又是如何进行注入的呢?

Spring 通过一个配置文件或注解来描述 Bean 和 Bean 之间的依赖关系,根据 Bean 配置信息在容器内部创建Bean定义注册表,根据注册表加载、实例化 Bean、建立Bean与Bean之间的依赖关系,还提供了 Bean 实例缓存、生命周期管理、Bean 实例代理、事件发布、资源装载等高级服务。

Spring 框架和核心接口是 Bean 工厂,工厂分为两种:

  • BeanFactory:这是最基础的,面向 Spring 的
  • ApplicationContext:是面向 Spring 框架开发者的,几乎所有的应用场合都可以使用它。

BeanFactory 有着庞大的继承、实现体系,有众多的子接口、实现类。

来看一下 BeanFactory 的基本类体系:

ApplicationContext 的继承体系:

  • BeanFactory 负责读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的声明周期。

  • ApplicationContext 除了提供上述 BeanFactory 所能提供的功能之外,还提供了更完整的框架功能:

    1. 国际化支持
    2. 资源访问:Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
    3. 事件传递:通过实现ApplicationContextAware接口

介绍了 Bean 的工厂类,再来详细的解答下 Spring 是如何管理这些类的?这个问题。

Bean的生命周期

Bean 的生命周期可以说是面试的时候常问的问题之一,我们可以用下面两张图直观的感受下:

BeanFactory 中 Bean 的生命周期:

ApplicationContext 中 Bean 的生命周期:

可以看到在进行实例化、设置属性值、通过 init-method 属性配置的初始化方法、放入 Spring 缓冲池 前后都有方法可以对 Bean 进行再次加工。

Bean 的生命周期从开始到最终销毁其中经历的方法可以分为四类:

  • Bean自身的方法:如调用 Bean 构造函数实例化 Bean,调用 Setter 设置 Bean 的属性值以及通过的 init-method 和 destroy-method 所指定的方法;
  • Bean级生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,这些接口方法由 Bean 类直接实现;
  • 容器级生命周期接口方法: InstantiationAwareBean PostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“ 后处理器” 。这些后处理器的影响是全局性的。用户可以通过合理地编写后处理器,让其仅对感兴趣Bean 进行加工处理。
  • 工厂后处理器接口方法

Bean 的装配过程

简单叙述下 Bean 的装配过程:

  • BeanDefinitionReader 读取 Resource 所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个 BeanDefinition 对象,并保存到 BeanDefinitionRegistry 中;
  • 容器扫描 BeanDefinitionRegistry 中的BeanDefinition;调用InstantiationStrategy 进行Bean实例化的工作;使用 BeanWrapper 完成Bean属性的设置工作;
  • 单例Bean缓存池:Spring 在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用HashMap实现的缓存器,单实例的 Bean 以beanName为键保存在这个HashMap中。

ApplicationContext 和 BeanFactory 的区别

区别:

  • BeanFactory 在启动的时候不会去实例化 Bean,中有从容器中拿 Bean 的时候才会去实例化,ApplicationContext 在启动的时候就把所有的 Bean 全部实例化了。它还可以为 Bean 配置 lazy-init=true 来让 Bean 延迟实例化;
  • BeanFacotry 是 spring 中比较原始的 Factory,无法支持 spring 的许多插件,如 AOP 功能、Web 应用等。 ApplicationContext接口
    • MessageSource, 提供国际化的消息访问
    • 资源访问,如URL和文件
    • 事件传播
    • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

Bean的装配方式

Bean 的装配方式有四种:

  • 基于 xml 的配置
  • 注解方式
  • java类的配置
  • 基于Groovy DSL的配置(不太常见)

一般都是用 xml 和 注解 两种配置,其他的方式使用的不多。这里不多做叙述

依赖注入的方式

依赖注入的方式有三种方式:

  • 属性注入:使用setter方法
  • 构造函数注入
  • 工厂方法注入

这其中属性注入的方式比较常用。

对象之间关系

对象之间有三种关系:

  • 依赖:挺少用的(在 xml 中使用depends-on就是依赖关系了-->前置依赖【依赖的Bean需要初始化之后,当前Bean才会初始化】)
  • 继承:
  • 引用:最常见(使用ref就是引用关系了)

Bean的作用域

  • singleton:单例,只有一个对象
  • prototype:多例,每次都会创建一个新对象
  • request:每次请求都会参数一个对象
  • session:每个session都会产生一个对象
  • global session:所有的session都用一个对象

默认Bean的作用域是单例的,其他需要使用不同类型的作用域,需要单独配置。

这里有个知识点 @lookup注解需要注意。如果我们需要在 singleton 的 Bean 中注入一个 prototype 的 Bean 并希望在每次调用 singleton 的 Bean 的时候获取的都是 prototype 的 Bean ?这就需要使用 @lookup注解。

IOC 的优缺点

  • 优点:实现组件之间的解耦,提高程序的灵活性和可维护性。
  • 缺点:生成一个对象的步骤变复杂了,生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。

总结

这里,再次做一个知识点的总结:

  • IoC 是一种概念和设计思想,Guice,Spring,Jboss都实现了这种理念,Spring将其发扬光大

  • Spring IoC 容器实现基于反射技术

  • IoC 的3种类型,Spring IOC 支持2种,构造函数注入和属性注入

  • Spring 容器工厂分为两种:ApplicationContext、BeanFactory。异同点:1. BeanFactory 在启动的时候不会去实例化 Bean,ApplicationContext 在启动的时候会。2. BeanFacotry 是 spring 中比较原始的 Factory,无法支持 spring 的许多插件,如 AOP 功能、Web 应用等。 ApplicationContext 支持。

  • Bean的生命周期的过程:

    • 四类方法:
      • Bean自身的方法:如Setter init-method 和 destroy-method 所指定的方法;
      • Bean级生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,这些接口方法由 Bean 类直接实现;
      • 容器级生命周期接口方法: InstantiationAwareBean PostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“ 后处理器” 。这些后处理器的影响是全局性的。用户可以通过合理地编写后处理器,让其仅对感兴趣Bean 进行加工处理。
      • 工厂后处理器接口方法
  • Bean 的装配过程:

    • BeanDefinitionReader 读取 Resource 所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个 BeanDefinition 对象,并保存到 BeanDefinitionRegistry 中;
    • 容器扫描 BeanDefinitionRegistry 中的BeanDefinition;调用InstantiationStrategy 进行Bean实例化的工作;使用 BeanWrapper 完成Bean属性的设置工作;
    • 单例Bean缓存池:Spring 在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用HashMap实现的缓存器,单实例的 Bean 以beanName为键保存在这个HashMap中。
  • bean的装配方式,四种:1. xml;2. 注解;3. java类的配置;4. 基于Groovy DSL的配置(不太常见)

  • 依赖注入的方式,三种:1. 属性注入:通过setXxx()方法注入Bean的属性值;2. 构造器注入:通过构造函数注入;3. 工厂方法注入:用静态工厂或非静态工厂方法注入

  • bean的五个作用域:1. singleton:单例,只有一个对象;2. prototype:多例,每次都会创建一个新对象;3. request:每次请求都会参数一个对象;4. session:每个session都会产生一个对象;5. global session:所有的session都用一个对象

  • 高级主题:1. 国际化:多语言;2. 容器事件:监听对应的事件并做出反应,比如:RequestHandledEvent,当一个Http请求被处理后产生事件;3. 引用外部属性文件:数据库的用户名密码信息存储在properties文件中,可以使用${XX} 进行引用;4. 属性编辑器:转化为基本类型的利器

最后再附上Spring-Ioc的思维导图,建议点赞、关注、收藏,以便随时复习巩固知识:

我是帅帅,一个在互联网苟且偷生的工具人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章