本文适合初学者,也适合老手巩固以前的知识,针对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 所能提供的功能之外,还提供了更完整的框架功能:
- 国际化支持
- 资源访问:
Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
- 事件传递:通过实现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的思维导图,建议点赞、关注、收藏,以便随时复习巩固知识:
我是帅帅,一个在互联网苟且偷生的工具人。