4

Spring的启动流程中,bean的加载是一个重要而复杂的过程。其间有非常多精妙的处理。

Spring启动分析之构造器的多种情况:

Spring管理下的bean,使用构造函数去初始化bean是一种非常常见的情况。但是使用构造函数本身去初始化bean的情况多种多样,且面对不同情况,Spring采用的方式也各有不同。下面将件数Spring初始化的流程中,构造方法的选择情况。

总的来说,Spring的构造方法,总体分两类,第一类是普普通通的构造函数,第二类则是有@Autowired注解的构造函数。下面的列表展示了Spring启动的时候,对于要初始化的bean,其可能的构造函数的具体情况。

在构造函数没有被@Autowired注解的情况下,具体情况大致分为:

  1. 只有一个无参构造函数。
  2. 只有一个含参构造函数。
  3. 有若干个构造函数,但是包含一个无参构造函数。
  4. 有若干个构造函数,但是不包括一个无参构造函数。

总体上来说,对于上面的四种情况处理的非常简单。当Spring只有一个构造函数的时候,将会直接使用这个构造函数来进行初始化。无论这个构造函数究竟是含参的还是不含参的,但是对于含参的构造函数来说,构造器所依赖的必须要被满足。

在有多个构造函数的情况下,也非常简单,如果构造函数中有无参构造函数,那么使用无参构造函数进行初始化。如果没有构造函数,那么Spring将会直接抛出异常,提示用户该bean无法实例化,需要用户指明Spring要选用哪一个构造方法,或者选用哪几个构造方法。指明选用的构造器的方法也很简单,在构造器上加上@Autowired表明你希望Spring使用这个构造器来进行初始化构造。

在构造函数被@Autowired注解的情况下,@Autowired可以通过true、false来设置强依赖或者弱依赖,具体情况大致分为:

  1. 只有一个@Autowired修饰的构造函数。
  2. 有多个一个@Autowired或修饰的构造函数。

对于第一种情况,非常的简单,如果使用@Autowired注解了这个构造器,那么将会使用这个构造器进行构造。在@Autowired的required属性为true的使用,强制使用这个构造函数。如果required属性为false的话,那么在这个构造函数所需要的参数不被满足的情况下,如果有默认的无参构造函数,Spring还会调用默认的无参构造函数来进行初始化。如果没有默认的无参构造函数,那么非常抱歉,将会抛出异常。

如果有多个@Autowired注解的构造函数,如果这里面存在required=true的构造函数,那么将会直接抛出异常。如果有@Autowired(required=true)注解构造函数,那么这个bean有且只能有这一个被@Autowired注解的构造函数。如果有多个,无论其它的是不是@Autowired(required=false),都会被认作是多余的,将直接抛出异常。

如果Spring发现了一个bean有多个@Autowired(required=false)修饰的构造函数。那么Spring将会在创建一个构造器的候选列表,通过在这个列表中进行筛选,选择Spring认为最合适的构造方法。如果该类中还有一个默认的构造函数,那么还会将这个默认的构造函数放入到构造器的候选列表当中去,以防在所有的@Autowired都没有被满足的情况下,仍旧能够使用默认的无参构造函数进行构造。

Spring对Kotlin进行了特别的处理,在Kotlin中还有首选构造函数。如果Spring发现了Kotlin中的首选构造函数,还会根据首选构造函数进行一些处理,大致和无参构造函数类似。

在有多个@Autowired(required=false)的时候,Spring采用差异度算法,会对这些构造器进行特别的处理。

Spring针对多个@Autowired(required=false)进行选择时所采取的算法:
Spring在面对多个构造函数的时候,首先会对这些构造函数根据参数的个数和权限的大小(“public default protected private”)进行排序。然后按照先后顺序进行处理。(先差异度,后顺序)

Spring对多个构造函数,采用差异度的大小来进行排序,然后选择差异度最小的那个构造函数进行初始化。如果有多个差异度相同的构造函数,那么按照处理的顺序,采用最小差异度中第一个被处理的构造函数。

计算差异度的方法其实也不难。

首先,要被注入的构造函数,它们所需的参数必须存在于Spring的容器当中。如果连这个都不满足,那么这个构造函数将会被直接PASS。

然后,对构造函数进行循环处理。对于每一个构造函数。首先根据具体情况,从容器中将能够满足它们的依赖全部取出来。假设有一个构造函数是 public Constructor(java.lang.Number);。而Spring容器中又正好存在java.lang.Float,java.lang.Integer这两个bean,Spring将会将这两个bean提取出来,与之进行匹配。在匹配的时候,Spring将会使用继承层次树上最近的那一个进行匹配。

比如说构造器是public Constructor(java.lang.Object),而Spring容器中又正好有java.lang.Object,java.lang.String,java.lang.Integer三个bean,那么将使用java.bean来进行匹配。当一个函数的所有需要的参数都被提取出来的时候,开始对这个构造器进行差异度的计算。

差异度的计算方法依旧是根据继承关系来进行的。对于每一个构造器,其构造函数有若干个参数,假设它们的类型分别为(ClazzA,ClazzB,ClazzC...)。而参数的类型分别是(ParamA,ParamB,ParamC...)。首先可以确定的是,经过了层层的淘汰与计算。对于剩下的构造器,它们的构造函数参数都是可以满足的。但是类型未必是完全匹配的。比如说,需要的构造函数是(Object,Object,Object),而实际传入的参数可能是(Integer,Float,String)。那么原来的构造函数和实际情况是有一定的差异的,Spring将这个差异通过一个数字来表示,我们称之为差异度指数。

对于Spring现在所可以选用的构造器。通过计算,每一个构造器的可以产生一个差异度指数。Spring将会选择差异度指数最小的构造函数,如果有多个差异度相同的构造函数,那么选择在列表中的第一个(即前面所说的第一个处理的构造器函数)。

对于任意一个构造器函数,其具有n个参数。对于第i个声明的参数,我们假设分别为Ci,而具体传入的参数,我们则认为是Pi。然后Spring会计算出Pi所对应的类型,与实际Ci的类型的继承关系,来产生一个差异度。最后将这个构造器的n个参数的差异度全部求和,得到这个构造器的总体差异度。差异度的计算犯法很简单。继承每经过一个层,差异度将会增加2。比如实际需要的参数是Object,而传入的参数是Float,那么它们的差异度则是4。因为Float继承了Number,而Number则继承了Object,所以从Float到Object,一共经历了两层继承。每一层继承会带来数量为2的差异度,两层继承,那么差异度则为4。如果参数是一个接口的话,那么差异度还会额外增加1。

以构造器Constructor(Object,Object,Object),实际参数为(Float,Double,Integer)为例。因为Float,Double,Integer均继承于Number,并且Number继承于Object,且Object不为接口。所以该构造器的差异度指数为6。

以此类推,对于每一个构造器,Spring都能根据继承关系算出它们的差异度,然后通过Spring差异度进行排序,并选择其中差异度最低的那个,来实例化bean。

Spring的源码中,对于构造器为Set,List,Map类型也进行了特殊的处理,所以Spring也能够通过使用Set,List,Map进行注入多余一个相同类型的bean。


秣城摄影师
4 声望1 粉丝