1 什么是IoC
IoC(Inversion of Control),即控制反转,是一种思想,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
传统程序设计:通过代码(new等)主动创建对象,创建对象的依赖对象,并主动关联依赖对象
IoC:由专门一个容器来创建这些对象,自动创建对象的依赖对象并注入,且管理这些对象的生命周期
2 IoC的作用
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试
IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松耦合,这样也方便测试,利于功能复用
从思想上,发生了“主从换位”的变化。应用程序原本要获取什么资源都是主动去获取,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源。即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
3 Ioc和DI
DI(Dependency Injection),即依赖注入,所谓依赖注入,就是比如某个对象需要另一个对象,则容器在运行的时候,创建这个依赖,并将这个依赖注入到该对象中。组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入提高了组件的复用性。我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
Ioc和DI的关系:
它们是同一个概念的不同角度描述,依赖注入比控制反转更加易于理解。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象,这一点是通过DI来实现的。
4 IoC的优缺点
优点:
- 对象与对象之间是松耦合,复用性好
- 可维护性比较好
缺点:
- 由于引入了第三方IOC容器,生成对象的步骤变得有些复杂
- 对象生成因为是使用反射编程,在效率上有些损耗
5 IoC容器
springIoC的实现使用了反射与工厂模式
Spring中定义了两个接口用以表示容器:
- BeanFactory
- ApplicationContext
BeanFactory 可以称之为 “低级容器”,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。最主要的方法就是getBean(String beanName)
ApplicationContext 可以称之为 “高级容器”。ApplicationContext 建立在BeanFactory的基础上,因为除了能够提供IoC容器的基本功能外,他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。
- 支持信息源,可以实现国际化。(实现MessageSource接口)
- 访问资源。(实现ResourcePatternResolver接口,这个后面要讲)
- 支持应用事件。(实现ApplicationEventPublisher接口)
所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。
几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory
该接口定义了一个 refresh 方法,用于刷新整个容器,即重新加载/刷新所有的 bean。
6 IoC容器的初始化过程
IoC容器的初始化包括三个基本的过程:
- Resource定位(Bean的定义文件定位)
- 将Resource定位好的资源载入到BeanDefinition
- 将BeanDefiniton注册到容器中
- 第一步 Resource定位
Resource是什么:是Sping中用于封装I/O操作的接口,创建spring容器时,通常要访问XML配置文件,除此之外还可以通过访问文件类型、二进制流等方式访问资源,还有当需要网络上的资源时可以通过访问URL,Spring把这些文件统称为Resource
ApplicationContext的所有实现类都实现RecourceLoader接口,因此可以直接调用getResource(参数)获取Resoure对象
- 第二步 通过返回的resource对象,进行BeanDefinition的载入
BeanDefinition:SpringIOC容器管理了我们定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源对象中的bean而来的,IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。
在Spring中配置文件主要格式是XML,IoC 容器会使用到AbstractXmlApplicationContext类的loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于获取BeanDefinition
此方法在具体执行过程中使用一个BeanDefinitionReader型实例对象,来进行使用BeanDefinitionReader的loadBeanDefinitions方法载入Resource。对resource进行处理,并生成BeanDefinition对象。
- 第三步,将BeanDefiniton注册到容器中
将BeanDefinition注册到DefaultListableBeanFactory.beanDefinitionMap中。key是类名,value是实例对象。
7 IOC容器的依赖注入
当Spring IoC容器完成了Bean定义资源的定位、载入和解析注册以后,IoC容器中已经管理类Bean定义的相关数据,但是此时IoC容器还没有对所管理的Bean进行依赖注入,依赖注入发生在:
用户第一次通过getBean方法向IoC容索要Bean时,IoC容器触发依赖注入
对于单例的bean,容器会在初始化的时候就进行依赖注入,但是如果在该Bean定义时配置了lazy-init属性,即延迟加载,则该单例bean的依赖注入发生在用户第一次调用getBean方法获取该bean时
依赖注入有三种注入方式:构造器、接口、set注入。我们常用的是set注入
8 Bean的作用域
singleton
spring中的bean的默认作用域,当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,即单例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
prototype
Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean() 方法)时都会创建一个新的bean实例。根据经验,对所有有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用 singleton作用域
request
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用 域仅在基于web的Spring ApplicationContext情形下有效。
session
在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global session
在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于 web的Spring ApplicationContext情形下有效。
9 Bean的生命周期
Bean实例生命周期的执行过程如下:
- Spring对bean进行实例化,默认bean是单例;
- Spring对bean进行依赖注入;
- 如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;
- 如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;
- 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;
- 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;
- 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,则再调用该方法;
- 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;
- 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;
- 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。如果bean使用了destroy-method属性声明了销毁方法,则再调用该方法;