IoC的英文是Inversion of Control ,他是面向对象中的一种编程设计思想。其翻译成中文的意思是“控制反转”,那么如何解释控制反转呢?那么我们需要先回到原生的开发过程中,加入我们的A类依赖B类,那我们就要主动去创建B的对象和属性赋值,然后才能使用这个对象。但是对于IoC这种方式,像创建对象,属性赋值这些细节工作都交由IoC容器来完成,我们只需关注对象的使用,通过这种方式达到解藕。
那么,把对象都交给了IoC容器管理,那A类想使用B类的时候怎么使用呢?Spring中有依赖查找和依赖注入两种方式,其中常用的是依赖注入,也就是Dependency Injection俗称DI,而依赖查找的英文是Dependency Lookup简称DL。也就是说DI和IoC的关系是,DI是IoC的一种实现方式。
那IoC都做了哪些工作呢?主要有下面三个,一是管理资源的生命周期,比如Java Bean;二是依赖处理,通过依赖查找或依赖注入;三是管理配置,像容器配置、外部配置、托管的资源配置。
既然我们的对象全部托管给了IoC容器,那么IoC容器是如何管理对象的生命周期的呢?IoC容器中对象的生命周期包含这么5个阶段:
<1>元信息处理阶段,这个阶段又分为元信息配置、元信息解析、元信息注册。其中元信息配置我们常用的有xml配置bean的方式、还有注解的方式像@Service;元信息解析就是对配置的元信息解析为BeanDefinition对象;元信息注册就是把BeanDefinition对象保存到BeanDefinitionRegistry 的 ConcurrentHashMap 集合中。
<2>实例化阶段,这个阶段包括BeanDefinition合并和Bean的实例化。其中,BeanDefinition合并是因为定义的 Bean 可能存在层次性关系,则需要将它们进行合并,存在相同配置则覆盖父属性,最终生成一个 RootBeanDefinition 对象;Bean的实例化是通过类加载器加载出一个 Class 对象,通过这个 Class 对象的构造器创建一个实例对象,构造器注入在此处会完成。
<3>Bean属性赋值阶段,在 Spring 实例化后,需要对其相关属性进行赋值,注入依赖的对象。首先获取该对象所有属性与属性值的映射,可能已定义,也可能需要注入,在这里都会进行赋值(反射机制)。
<4>Bean初始化阶段,这个阶段包括Aware接口回调、Bean初始化、初始化完成。其中,Aware接口回调是在 Spring 实例化后,如果 Spring Bean 是 Spring 提供的 Aware 接口类型,这里会进行接口的回调,注入相关对象;初始化完成是在所有的 Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring 会再次遍历所有初始化好的单例 Bean 对象。
<5>销毁阶段,当 Spring 应用上下文关闭或者你主动销毁某个 Bean 时则进入 Spring Bean 的销毁阶段。
我们在编程过程中对象的使用有时候是比较复杂的,比如A依赖B,B又依赖A;再比如A依赖B、C,B和C又分别依赖A,那IoC容器是如何解决这种循环依赖的呢?这里就要说到在创建Bean的时候使用的三级缓存:
<1>一级缓存singletonObjects,用于保存实例化、注入、初始化完成的bean实例;
<2>二级缓存earlySingletonObjects,用于保存实例化完成但是未初始化的bean实例;
<3>三级缓存singletonFactories,里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好),如果存在则将获取到的 Bean 对象并保存至二级缓存,同时从当前三级缓存移除该 ObjectFactory 实现类。
我们来看第一种循环依赖:A依赖B,B又依赖A情况,IoC容器是如何解决的。假设,
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
那么TestService1和TestService2的对象创建流程是什么流程呢?复制网友的一张图:
当我们去依赖查找 testService1,在实例化后初始化前会先生成一个 ObjectFactory 对象(可获取当前正在初始化 testService1)保存在上面的 singletonFactories 中,初始化的过程需注入 testService2;接下来去查找 testService2,初始 testService2 的时候又要去注入 testService1,又去查找 testService1 ,由于可以通过 singletonFactories 直接拿到正在初始化的 testService1,那么就可以完成 testService2的初始化,最后也完成 A 的初始化,这样就避免出现循环依赖。
但是上面流程好像二级缓存的作用不大,那为什么要有个二级缓存呢?
我们看上面说的第二种循环依赖,A依赖B、C,B和C又分别依赖A。假设,
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Autowired
private TestService3 testService3;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
public void test3() {
}
}
假如没有二级缓存,流程是下面这样,这里省略了testSerivce2的初始化:
TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。而TestService1注入到TestService2点时候一级实例化过一次了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。
那么有了二级缓存后,正确的流程是这样的:
TestService1在注入到TestService2的时候已经进行了实例化并从三级缓存移到二级缓存了,当TestService1在注入到TestService3的时候直接从二级缓存读取,这就和注入到TestService2的实例是同一个了。
到这里简单介绍了IoC的概念,对Bean的生命周期管理以及循环依赖的解决。但是还有一些问题,比如IoC容器的对象默认都是单例的,而web应用都是并发请求的,那么如何解决这个问题呢?下一篇再简单介绍。
参考资料:
死磕Spring之IoC篇 - 深入了解Spring IoC(面试题) - 月圆吖 - 博客园
Spring 是如何解决循环依赖的? - 知乎



