Spring自动注入原理

spring的属性注入属于spring bean的生命周期一部分,bean的生命周期首先记住两个概念:

  1. spring bean:最终存在spring容器当中的对象
  2. 对象:实例化出来的对象,但是一个对象并不一定是spring bean

所谓的bean的生命周期就是磁盘上的类通过spring扫描,然后实例化,属性注入,跟着初始化,继而放到容器当中的大概过程

finishBeanFactoryInitialization

通过finishBeanFactoryInitialization初始化我们的单例非懒加载的Bean,创建一个Z.java来看看

首先实例化ApplicationContext容器对象

会调用构造方法里的refresh来初始化容器

执行完前后可以看到,在方法finishBeanFactoryInitialization里,Spring已经帮我们创建好了Bean,也就是bean的生命周期都在此方法内

通过此方法内的注释可以知道,接着会执行finishBeanFactoryInitialization#beanFactory.preInstantiateSingletons().doGetBean(beanName);方法

doGetBean

doGetBean方法内容有点多,这个方法非常重要,整个spring bean生命周期中这个方法有着举足轻重的地位,开始分析doGetBean的执行流程

第一个getSingleton

getSingleton(beanName) 是实现自动注入最主要的方法,spring初始化bean的时候先判断bean是否在容器当中,如果存在,直接返回,如不存在则生产。同时,这个方法也是API getBean(beanName) 的底层实现。
spring在初始化的时候容器当中肯定是没有Z的,为什么还需要去判断一下呢?因为一个bean被put到单例池(容器)的渠道有很多;除了spring容器初始化—扫描类—-实例化—–put到容器这条线之外还有很多方法可以把一个对象put到单例池,比如:

这里是第一次从单例池中拿Z,肯定是null,所以 singletonObject == null 成立

判断Z是否在正在创建的集合里,第一次肯定是不在的,isSingletonCurrentlyInCreation == false 不成立

以上判断完成,会继续往下面走,继续实例化,然后下面又调用了一次getSingleton,上面也调用了一次getSingleton,两个getSingleton方法并不是同一个方法,这是方法重载,第二次getSingleton就会把bean创建出来

第二个getSingleton

第二次getSingleton就会把我们bean创建出来,换言之整个bean如何被初始化的都是在这个方法里面

当spring觉得可以着手来创建bean的时候首先便是调用beforeSingletonCreation(beanName); 判断当前正在实例化的bean是否存在正在创建的集合当中

当代码运行完this.singletonsCurrentlyInCreation.add(beanName)之后可以看到singletonsCurrentlyInCreation集合当中只存在一个Z,并且没有执行z的构造方法,说明spring仅仅是把Z添加到正在创建的集合当中,但是并没有完成bean的创建(因为连构造方法都没调用)

当运行完this.singletonsCurrentlyInCreation.add(beanName) 之后结果大概如下图这样

之后就会走到singletonObject = singletonFactory.getObject();这个调用的是createBean(beanName, mbd, args) 方法;把创建好的spring bean返回出来;至此第二次getSingleton方法结束

createBean

createBean()方法中调用了doCreateBean方法创建spring bean

doCreateBean

createBeanInstance:创建对象

运行完createBeanInstance之后控制台打印了Z的构造方法的内容,说明Z对象已经被创建了,但是这个时候的Z不是spring bean,因为spring bean的生命周期才刚刚开始

把前面知识串起来,画一下当前代码的语境

这个createBeanInstance方法是如何把对象创建出来的呢?大致流程是:

  1. 如果是自动装配,推断出来各种候选的构造方法
  2. 利用推断出来的候选构造方法去实例化对象
  3. 如果没有推断出合适的构造方法(或者没有提供特殊的构造方法),则使用默认的构造方法
  4. 利用默认的构造方法,使用反射去实例化对象

populateBean:属性自动注入

实例化完成后,开始执行方法populateBean完成属性自动注入,我们在Z里面添加自动注入X

首先看一下方法执行前的情况

没有执行populateBean之前只实例化了Z,X并没实例化;接下来看看执行完这行代码之后的情况

Z 填充 X (简称 zpx)首先肯定需要获取X,调用getBean(x),getBean的本质上文已经分析过,就是调用getSingleton(beanName),进入到第一次调用getSingleton。第一次getSingleton会从单例池获取一下X,如果X没有存在单例池则开始创建X,创建X的流程和创建Z一模一样,都会走bean的生命周期;比如把X添加到正在创建的bean的集合当中,推断构造方法,实例化X

这样就完成了X的自动注入

循环依赖

Z 填充 X,但是X对象里又自动注入Z

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Z {

@Autowired
private X x;

public Z() {
System.out.println("z create");
}

}
1
2
3
4
5
6
7
8
9
10
11
@Component
public class X {

@Autowired
private Z z;

public X() {
System.out.println("x create");
}

}

是否能够获取到Z呢?首先我们想如果获取失败则又要创建z—>实例化z—填充属性—-获取x—-就无限循环了;所以结果是完成了循环依赖,所以这里肯定能够获取到Z,为什么呢?
联系上文第一次调用getSingleton是无法获取到Z的?因为上面说过第一次调用getSingleton是从单例池当中获取一个bean,但是Z显然没有完成生命周期(Z只走到了填充X,还有很多生命周期没走完)
所以应该是获取不到的?为了搞清楚这个原因得去查看第一次getSingleton的源码

总结:
首先spring从单例池当中获取Z,前面说过获取不到,然后判断是否在正在创建bean的集合当中,前面分析过这个集合现在存在Z和X;所以if成立进入分支;进入分支,spring直接从三级缓存中获取Z,根据前面的分析三级缓存当中现在什么都没有,故而返回null
进入下一个if分支,从二级缓存中获取一个ObjectFactory工厂对象;二级缓存中存在Z和X,故而可以获取到;跟着调用singletonFactory.getObject();拿到一个半成品的bean Z对象;然后把Z对象放到三级缓存,同时把二级缓存中Z清除(此时二级缓存中只存在一个X了,而三级缓存中多了一个Z)

问题:

  1. 为什么首先是从三级缓存中取呢?主要是为了性能,因为三级缓存中存的是一个Z对象,如果能取到则不去二级找了
  2. 为什么一开始不直接存三级缓存呢?如果直接存到三级缓存,只能存一个对象,假设以前存这个对象的时候这对象的状态为za,但是我们这里X要注入的z为zc状态,那么则无法满足
  3. 二级有什么用呢?为什么一开始要存工厂呢?但是如果存一个工厂,工厂根据情况产生任意za或者zb或者zc等等情况;比如说AOP的情况下z注入x,x也注入z,而x中注入的z需要加代理(AOP),但是加代理的逻辑在注入属性之后,也就是z的生命周期走到注入属性的时候z还不是一个代理对象,那么这个时候把z存起来,然后注入x,获取、创建x,x注入z,获取z;拿出来的z是一个没有代理的对象;但是如果存的是个工厂就不一样;首先把一个能产生z的工厂存起来,然后注入x,注入x的时候获取、创建z,x注入z,获取z,先从三级缓存获取,为null,然后从二级缓存拿到一个工厂,调用工厂的getObject();spring在getObject方法中判断这个时候z被AOP配置了故而需要返回一个代理的z出来注入给x,总之getObject会根据情况返回一个z,但是这个z是什么状态,spring会自己根据情况返回
  4. 为什么要remove二级缓存?如果存在比较复杂的循环依赖可以提高性能;比如x,y,z相互循环依赖,那么第一次x注入z的时候从二级缓存通过工厂返回了一个z,放到了三级缓存,而第二次y注入z的时候便不需要再通过工厂去获得z对象了.因为if分支里面首先是访问三级缓存;至于remove则是为了gc吧

initializeBean:初始化

InitializingBean 对应生命周期的初始化阶段,实例化和属性赋值都是Spring帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段.Aware方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用Aware接口获取的资源,这也是我们自定义扩展Spring的常用方式

这几种定义方式的调用顺序没有必要记.因为这几个方法对应的都是同一个生命周期,只是实现方式不同


参考:
博客

------本文结束感谢阅读------
0%