0%

Spring IoC原理

控制反转IoC(Inverse of Control)与依赖注入DI(Dependency Injection):依赖注入和控制反转是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同。依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

其实IoC/DI对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC/DI容器来创建并注入它所需要的资源了。

这么小小的一个改变其实是编程思想的一个大进步,这样就有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

BeanFactory与ApplicationContext

Spring IoC有两个主要的容器系列:

  1. 实现BeanFactory接口的简单容器系列,这一系列容器只实现了容器的最基本功能
  2. ApplicationContext应用上下文,它作为容器的高级形态存在。应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境做了许多适配

BeanFactory是IoC容器体系结构中最基本的接口类
BeanDefinition是对依赖反转模式中管理对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构

BeanFactory接口定义了基本的IoC容器的规范,包含了getBean()这样的IoC容器的基本方法(通过这个方法可以从容器中取得Bean)。HierarchicalBeanFactory接口继承了BeanFactory接口之后,增加了getParentFactory()接口功能,使BeanFactory具备了双亲IoC管理的功能。在ConfigurableBeanFactory接口中,定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory()设置双亲IoC容器,通过addBeanPostProcessor()配置bean后置处理器等等。通过这些接口设计的叠加,定义了BeanFactory就是简单IoC容器的基本功能。

在ApplicationContext设计中,一方面它继承了BeanFactory接口体系中的ListableBeanFactor、HierachicalBeanFactory等BeanFactory的接口,具备了BeanFactory IoC的基本功能(并未继承AutowireCapableBeanFactory接口,因为几乎不会被程序用到,但是可以通过ApplicationContext的getAutowireCapableBeanFactory()获得);另一方面,通过继承MessageSource、ResourceLoader、ApplicationEventPublisher这些接口,为ApplicationContext赋予更高级的IoC容器特性。

BeanFactory容器设计原理(以XmlBeanFactory为例)

DefaultListableBeanFactory包含了基本IoC容器的重要功能,是很多地方被用到的容器系列中的基本产品,Spring实际把DefaultListableBeanFactory作为默认的功能完整的IoC容器来使用。XmlBeanFactory继承了DefaultListableBeanFactory容器功能的同时,可以读取以XML文件定义的BeanDefinition。

XmlBeanFactory初始化了一个XmlBeanDefinitionReader的对象,由这个Reader对象从Xml文件中读取BeanDefinition信息。

构造XmlBeanFactory容器时,需要指定BeanDefinition信息的来源,这个来源由封装成Spring Resource的对象提供。Resource是Spring用来封装I/O操作的类。

ApplicationContext新特性

ApplicationContext提供了BeanFactory不具备的新特性,这些新特性使得对ApplicationContext的使用是一种面向框架的使用风格,一般建议在开发时使用ApplicationContext作为IoC容器的基本形式。ApplicationContext的新特性包括:

  1. 支持不同的信息源,通过扩展MessageSource接口支持国际化的实现,为开发多语言版本的应用提供服务。
  2. 访问资源,可以从不同地方得到Bean定义资源
  3. 支持应用事件,继承接口ApplicationEventPublisher,从而在上下文引入事件机制。这些事件和Bean生命周期的结合为Bean管理提供便利。

ApplicationContext设计原理(以FileSystemXmlApplicationContext为例)

FileSystemXmlApplicationContext的主要功能在基类AbstractXmlApplicationContext中实现,作为一个具体的应用上下文,只需要实现和它自身设计相关的两个功能。

  1. 启动IoC容器的refresh()过程,该过程涉及IoC容器启动的一系列复杂操作。
  2. 提供getResourceByPath(),得到FileSystemResource的资源定位。

Bean构造过程

image-20190902151512011

  1. Spring对scope为singleton且非懒加载的bean进行实例化
  2. Spring将值和bean的引用注入到bean对应的属性中
  3. 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName方法
  4. 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory接口,Spring将调用setBeanFactory方法
  5. 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext方法,将bean所在的应用上下文引用传进来
  6. 如果bean实现了BeanPostProcessor接口,spring将调用它们的postProcessBeforeInitialization方法
  7. 如果bean实现了InitializingBean接口,spring将调用它们的afterPropertiesSet方法;类似地,如果bean使用了init-method声明的初始化方法,该方法也会被调用
  8. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization方法
  9. 此时,bean已经准备就绪,可以被应用程序使用了。对于scope为singleton的Bean,Spring的ioc容器中会缓存一份该bean的实例,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;而对于scope为prototype的Bean,每次被调用都会new一个新的对象,其生命周期就交给调用方管理了,不再是Spring容器进行管理
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destory接口;同样,如果bean使用destory-method声明了销毁方法,该方法也会被调用

IoC容器初始化过程

  1. Resource定位过程。定位BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口完成。BeanDefinition资源定位的过程是由refresh触发的。
  2. BeanDefinition的载入。把用户定义好的Bean表示成IoC容器内部的数据结构,容器内部Bean的数据结构就是BeanDefinition。
  3. 向IoC容器注册BeanDefinition。这个过程通过调用BeanDefinitionRegistry接口实现的。IoC容器通过HashMap来管理BeanName和BeanDefinition。Bean和对应的BeanDefinition都是在beanDefinitionMap中被检索和使用,容器的作用就是对这些信息进行处理和维护,这些信息时容器建立依赖反转的基础。

IoC初始化过程一般不包含Bean依赖注入的实现。Spring IoC中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。但是如果对某个Bean设置lazyinit属性, 那么这个Bean的依赖注入在IoC容器初始化时就预先完成了,而不需要等到整个初始化完成以后,第一次使用getBean时才触发。

IoC容器依赖注入的过程

IoC容器经过初始化建立Bean和BeanDefinition之间的映射关系之后,用户通过getBean索要Bean时触发依赖注入过程(如果配置了lazy-init,则依赖注入发生在初始化过程中)。

依赖注入的过程发生在DefaultListableBeanFactory的基类AbstractBeanFactory的doGetBean函数中。处理依赖注入的过程为:

  1. 尝试从缓存中获取Bean,对于已经被创建的Singleton Bean,直接获取而不需要重复创建。
  2. 如果当前IoC容器中没有找到相应的Bean,则到双亲IoC容器中向上递归查找Bean
  3. 获取当前Bean依赖的所有Bean,递归进行查找和依赖注入的过程,直到没有依赖的Bean(DFS)
  4. 创建Singleton、prototype 类型实例
  5. 对创建的Bean进行类型检查,如果没有问题,就返回这个新创建的Bean,这个Bean已经是包含依赖关系的Bean

createBean过程

  1. 检查需要创建的Bean是否可以实例化,这个类是否可以通过类装载器载入.
  2. 如果Bean定义的单态模式(Singleton),则容器在创建之前先从缓存中查找,以确保整个容器中只存在一个实例对象。如果Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。除此之外,Bean定义还可以扩展为指定其生命周期范围。具体的Bean实例对象的创建过程由实现了ObejctFactory接口的匿名内部类的createBean方法完成,ObejctFactory使用委派模式,具体的Bean实例创建过程交由其实现类AbstractAutowireCapableBeanFactory完成。
  3. 生成Bean的方法有多种,可以通过工厂方法生成,也可以通过容器的autowire特性生成,这些生成方式都是由相关的BeanDefinition决定的。
  4. 如果Bean有方法被覆盖了,则使用JDK的反射机制进行实例化,否则,使用CGLIB进行实例化。instantiateWithMethodInjection方法调用SimpleInstantiationStrategy的子类CglibSubclassingInstantiationStrategy使用CGLIB来进行初始化。

依赖注入

  1. 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
  2. 对于非集合类型的属性,大量使用了JDK的反射和内省机制,通过属性的getter方法(reader method)获取指定属性注入以前的值,同时调用属性的setter方法(writer method)为属性设置注入后的值。

至此Spring IoC容器对Bean定义资源文件的定位,载入、解析和依赖注入已经全部分析完毕,现在Spring IoC容器中管理了一系列靠依赖关系联系起来的Bean,程序不需要应用自己手动创建所需的对象,Spring IoC容器会在我们使用的时候自动为我们创建,并且为我们注入好相关的依赖,这就是Spring核心功能的控制反转和依赖注入的相关功能。

Beanfactory 和 FactoryBean

BeanFactory 指的是 IOC 容器的编程抽象,比如 ApplicationContext, XmlBeanFactory 等,这些都是IOC 容器的具体表现,需要使用什么样的容器由客户决定, Spring 为我们提供了丰富的选择。 FactoryBean 是一个可以在 IOC而容器中被管理的一个 bean,是对各种处理过程和资源使用的抽象,FactoryBean 在需要时产生另一个对象,而不返回 FactoryBean本身,我们可以把它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的 Factory bean 都实现特殊的org.springframework.beans.factory.FactoryBean 接口,当使用容器中 factory bean 的时候,该容器不会返回 factory bean 本身,而是返回其生成的对象。

Spring 包括了大部分的通用资源和服务访问抽象的 Factory bean 的实现,其中包括:对 JNDI 查询的处理,对代理对象的处理,对事务性代理的处理,对 RMI 代理的处理等,这些我们都可以看成是具体的工厂,看成是SPRING 为我们建立好的工厂。也就是说 Spring 通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工重复的工作,我们要使用时只需要在 IOC 容器里配置好就能很方便的使用了。

参考资料

  1. Bean生命周期
    1. Spring中bean的作用域与生命周期
    2. 《Spring技术内幕(第二版)》
    3. Spring:源码解读Spring IOC原理
    4. java动态代理(JDK和cglib)