spring - DI
依赖注入发生的时间
当 Spring IOC 容器完成了 Bean 定义资源的定位、载入和解析注册以后,IOC 容器中已经管理类 Bean
定义的相关数据,但是此时 IOC 容器还没有对所管理的 Bean 进行依赖注入,依赖注入在以下两种情况
发生:
- 用户第一次通过 getBean 方法向 IOC 容索要 Bean 时,IOC 容器触发依赖注入。
- 当用户在 Bean 定义资源中为
<Bean>
元素配置了 lazy-init 属性, 默认是false,即让容器在解析注册 Bean 定义时进行预实例化,触发依赖注入。
BeanFactory 接口定义了 Spring IOC 容器的基本功能规范,是 Spring IOC 容器所应遵守的最底层和
最基本的编程规范。BeanFactory 接口中定义了几个 getBean 方法,就是用户向 IOC 容器索取管理的 Bean
的方法,我们通过分析其子类的具体实现,理解 Spring IOC 容器在用户索取 Bean 时如何完成依赖注
入。
在 BeanFactory 中我们看到 getBean(String…)函数,它的具体实现在 AbstractBeanFactory 中
AbstractBeanFactory 通过 getBean 向 IOC 容器获取被管理的 Bean
1 | //获取 IOC 容器中指定名称的 Bean |
1 | protected Object getObjectForBeanInstance( |
1 | //对 FactoryBean 的转义定义,因为如果使用 bean 的名字检索 FactoryBean 得到的对象是工厂生成的对象, |
1 | private final ThreadLocal<Object> prototypesCurrentlyInCreation = |
1 | protected void beforePrototypeCreation(String beanName) { |
1 | protected void afterPrototypeCreation(String beanName) { |
通过上面对向 IOC 容器获取 Bean 方法的分析,我们可以看到在 Spring 中,如果 Bean 定义的单例模式
(Singleton),则容器在创建之前先从缓存中查找,以确保整个容器中只存在一个实例对象。如果 Bean
定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。除此之外,Bean 定义还可以
扩展为指定其生命周期范围。
上面的源码只是定义了根据 Bean 定义的模式,采取的不同创建 Bean 实例对象的策略,具体的 Bean 实
例对象的创建过程由实现了 ObejctFactory 接口的匿名内部类的 createBean 方法完成,ObejctFactory
使用委派模式,具体的 Bean 实例创建过程交由其实现类 AbstractAutowireCapableBeanFactory 完成,
我们继续分析 AbstractAutowireCapableBeanFactory 的 createBean 方法的源码,理解其创建 Bean 实
例的具体实现过程。
循环依赖的产生和解决的前提
- A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象
- A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象
- A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象
当然,Spring对于循环依赖的解决不是无条件的,首先前提条件是针对scope单例并且没有显式指明不需要解决循环依赖的对象,而且要求该对象没有被代理过。同时Spring解决循环依赖也不是万能,以上三种情况只能解决两种,第一种在构造方法中相互依赖的情况Spring也无力回天。结论先给在这,下面来看看Spring的解决方法,知道了解决方案就能明白为啥第一种情况无法解决了。
Spring单例对象的初始化其实可以分为三步: - createBeanInstance, 实例化,实际上就是调用对应的构造方法构造对象,此时只是调用了构造方法,spring xml中指定的property并没有进行populate
- populateBean 填充属性,这步对spring xml中指定的property进行populate
- initializeBean 调用spring xml中指定的init方法,或者AfterPropertiesSet方法会发生循环依赖的步骤集中在第一步和第二步。
¶三级缓存
对于单例对象来说,在Spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了“三级缓存”。
“三级缓存”主要是指
1 | // 完美并且已经在使用的bean |
从字面意思来说:singletonObjects指单例对象的cache,singletonFactories指单例对象工厂的cache,earlySingletonObjects指提前曝光的单例对象的cache。以上三个cache构成了三级缓存,Spring就用这三级缓存巧妙的解决了循环依赖问题。
首先Spring会尝试从缓存中获取,这个缓存就是指singletonObjects,主要调用的方法是:
1 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
首先解释两个参数:
- isSingletonCurrentlyInCreation 判断对应的单例对象是否在创建中,当单例对象没有被初始化完全(例如A定义的构造函数依赖了B对象,得先去创建B对象,或者在populatebean过程中依赖了B对象,得先去创建B对象,此时A处于创建中)
- allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象,其实就是是否允许我重新创建一个
分析getSingleton的整个过程,Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取。如果获取到了则
1 | this.earlySingletonObjects.put(beanName, singletonObject); |
Spring解决循环依赖的诀窍就在于singletonFactories这个cache,这个cache中存的是类型为ObjectFactory,其定义如下:
1 | public interface ObjectFactory<T> { |
在bean创建过程中,有两处比较重要的匿名内部类实现了该接口。一处是
1 | new ObjectFactory<Object>() { |
另一处就是:
1 | addSingletonFactory(beanName, new ObjectFactory<Object>() { |
此处就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来的。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,长大成人,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象也蜕变完美了!
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!
AbstractAutowireCapableBeanFactory 创建 Bean 实例对象
AbstractAutowireCapableBeanFactory 类实现了 ObejctFactory 接口,创建容器指定的 Bean 实例对象,
同时还对创建的 Bean 实例对象进行初始化处理。其创建 Bean 实例对象的方法源码如下:
1 | //创建 Bean 实例对象 |
1 | / 虽然是remove方法 但是它的返回值也非常重要 |
通过对方法源码的分析,我们看到具体的依赖注入实现在以下两个方法中:
- createBeanInstance:生成 Bean 所包含的 java 对象实例
- populateBean :对 Bean 属性的依赖注入进行处理。
createBeanInstance 方法创建 Bean 的 java 实例对象
在 createBeanInstance 方法中,根据指定的初始化策略,使用静态工厂、工厂方法或者容器的自动装
配特性生成 java 实例对象,创建对象的源码如下:
1 | //创建 Bean 的实例对象 |
经过对上面的代码分析,我们可以看出,对使用工厂方法和自动装配特性的 Bean 的实例化相当比较清
楚,调用相应的工厂方法或者参数匹配的构造方法即可完成实例化对象的工作,但是对于我们最常使用
的默认无参构造方法就需要使用相应的初始化策略(JDK 的反射机制或者 CGLIB)来进行初始化了,在方
法 getInstantiationStrategy().instantiate 中就具体实现类使用初始策略实例化对象
SimpleInstantiationStrategy 类使用默认的无参构造方法创建 Bean 实例化对象
在使用默认的无参构造方法创建Bean的实例化对象时,方法getInstantiationStrategy().instantiate
调用了 SimpleInstantiationStrategy 类中的实例化 Bean 的方法,其源码如下:
1 | //使用初始化策略实例化 Bean 对象 |
通过上面的代码分析,我们看到了如果 Bean 有方法被覆盖了,则使用 JDK 的反射机制进行实例化,否
则,使用 CGLIB 进行实例化。
instantiateWithMethodInjection 方法调用 SimpleInstantiationStrategy 的子类
CglibSubclassingInstantiationStrategy 使用 CGLIB 来进行初始化,其源码如下:
1 | //使用 CGLIB 进行 Bean 对象实例化 |
CGLIB 是一个常用的字节码生成器的类库,它提供了一系列 API 实现 java 字节码的生成和转换功能。我
们在学习 JDK 的动态代理时都知道,JDK 的动态代理只能针对接口,如果一个类没有实现任何接口,要
对其进行动态代理只能使用 CGLIB。
populateBean 方法对 Bean 属性的依赖注入
在上面的分析中我们已经了解到 Bean 的依赖注入分为以下两个过程
- createBeanInstance:生成 Bean 所包含的 java 对象实例
- populateBean :对 Bean 属性的依赖注入进行处理
我们已经分析了容器初始化生成 Bean 所包含的 Java 实例对象的过程,现在我们继续分析
生成对象后,Spring IOC 容器是如何将 Bean 的属性依赖关系注入 Bean 实例对象中并设置好的,属性
依赖注入的代码如下
1 | //将 Bean 属性设置到生成的实例对象上 |
分析上述代码,我们可以看出,对属性的注入过程分以下两种情况:
- 属性值类型不需要转换时,不需要解析属性值,直接准备进行依赖注入
- 属性值需要进行类型转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。
对属性值的解析是在 BeanDefinitionValueResolver 类中的 resolveValueIfNecessary 方法中进行的,
对属性值的依赖注入是通过 bw.setPropertyValues 方法实现的,在分析属性值的依赖注入之前,我们
先分析一下对属性值的解析过程。
BeanDefinitionValueResolver 解析属性值
当容器在对属性进行依赖注入时,如果发现属性值需要进行类型转换,如属性值是容器中另一个 Bean
实例对象的引用,则容器首先需要根据属性值解析出所引用的对象,然后才能将该引用对象注入到目标
实例对象的属性上去,对属性进行解析的由 resolveValueIfNecessary 方法实现,其源码如下:
1 | //解析属性值,对注入类型进行转换 |
通过上面的代码分析,我们明白了 Spring 是如何将引用类型,内部类以及集合类型等属性进行解析的,
属性值解析完成后就可以进行依赖注入了,依赖注入的过程就是 Bean 对象实例设置到它所依赖的 Bean
对象属性上去,在第 7 步中我们已经说过,依赖注入是通过 bw.setPropertyValues 方法实现的,该方
法也使用了委托模式,在 BeanWrapper 接口中至少定义了方法声明,依赖注入的具体实现交由其实现类
BeanWrapperImpl 来完成,下面我们就分析依 BeanWrapperImpl 中赖注入相关的源码。
BeanWrapperImpl 对 Bean 属性的依赖注入
BeanWrapperImpl 类主要是对容器中完成初始化的 Bean 实例对象进行属性的依赖注入,即把 Bean 对象
设置到它所依赖的另一个 Bean 的属性中去,依赖注入的相关源码如下:
1 | //实现属性依赖注入功能 |
通过对上面注入依赖代码的分析,我们已经明白了 Spring IOC 容器是如何将属性的值注入到 Bean 实
例对象中去的:
- 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
- 对于非集合类型的属性,大量使用了 JDK 的反射和内省机制,通过属性的 getter 方法(reader method)获取指定属性注入以前的值,同时调用属性的 setter 方法(writer method)为属性设置注入后的值。看到这里相信很多人都明白了 Spring 的 setter 注入原理。
至此 Spring IOC 容器对 Bean 定义资源文件的定位,载入、解析和依赖注入已经全部分析完毕,现在
Spring IOC 容器中管理了一系列靠依赖关系联系起来的 Bean,程序不需要应用自己手动创建所需的对
象,Spring IOC 容器会在我们使用的时候自动为我们创建,并且为我们注入好相关的依赖,这就是
Spring 核心功能的控制反转和依赖注入的相关功能。