6.0 Spring推断构造方法
首先基础常识一个类通常有两个构造方法,一个有参的,一个无参的。对象创建默认使用无参构造。
在spring中,只有一个无参的构造方法,那么实例化就只能使用这个无参构造。
一、只一个有参构造
但只有一个有参的构造方法,那么实例化时能使用这个构造方法吗?要分情况了:
- 使用
AnnotationConfigApplicationContext
,会使用这个构造方法进行实例化,那么Spring会根据构造方法的参数信息去寻找bean,然后传给构造方法 - 使用
ClassPathXmlApplicationContext
,表示使用XML的方式来使用bean,要么在XML中指定构造方法的参数值(手动指定),要么配置autowire=constructor让Spring自动去寻找bean做为构造方法参数值。
二、有多个有参构造
如果有多个构造方法时又有两种情况:
多个构造方法中存不存在无参的构造方法,如何去确定到底用哪个构造方法呢?
- 如果开发者指定了想要使用的构造方法,那么就用这个构造方法
- 如果开发者没有指定想要使用的构造方法,则看开发者有没有让Spring自动去选择构造方法
- 如果开发者也没有让Spring自动去选择构造方法,则Spring利用无参构造方法,如果没有无参构造方法,则报错
针对第一点,指定构造方法的方式:
- xml中的标签,这个标签表示构造方法参数,所以可以根据这个确定想要使 用的构造方法的参数个数,从而确定想要使用的构造方法
- 通过@Autowired注解,@Autowired注解可以写在构造方法上,所以哪个构造方法上写了 @Autowired注解,表示开发者想使用哪个构造方法,当然,它和第一个方式的不同点是,通过 xml的方式,我们直接指定了构造方法的参数值,而通过@Autowired注解的方式,需要Spring 通过byType+byName的方式去找到符合条件的bean作为构造方法的参数值
针对第二点,只能在ClassPathXmlApplicationContext
方式上指定某个bean的autowire属性为constructor
另外在多个构造方法上写了@Autowired(required=true默认值)注解,那么此时Spring会报错
三、源码原理
AbstractAutowireCapableBeanFactory#createBeanInstance()
方法会去创建一个Bean 实例- 根据
BeanDefinition
加载类得到Class对象 - 如果
BeanDefinition
绑定了一个Supplier,那就调用Supplier的get方法得到一个对象并直接返 回 - 如果
BeanDefinition
中存在factoryMethodName
,那么就调用该工厂方法得到一个bean对象 并返回 - 如果
BeanDefinition
已经自动构造过了,那就调用autowireConstructor()
自动构造一个对象 - 调用
SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors()
方法得到哪些构造方法是可以用的 - 如果存在可用得构造方法,或者当前
BeanDefinition
的autowired
是AUTOWIRE_CONSTRUCTOR
,或者BeanDefinition
中指定了构造方法参数值,或者创建Bean 的时候指定了构造方法参数值,那么就调用autowireConstructor()
方法自动构造一个对象 - 最后,如果不是上述情况,就根据无参的构造方法实例化一个对象
autowireConstructor()
- 先检查是否指定了具体的构造方法和构造方法参数值,或者在
BeanDefinition
中缓存了具体的构造方法或构造方法参数值,如果存在那么则直接使用该构造方法进行实例化 - 如果没有确定的构造方法或构造方法参数值,那么
- 如果没有确定的构造方法,那么则找出类中所有的构造方法
- 如果只有一个无参的构造方法,那么直接使用无参的构造方法进行实例化
- 如果有多个可用的构造方法或者当前Bean需要自动通过构造方法注入
- 根据所指定的构造方法参数值,确定所需要的最少的构造方法参数值的个数
- 对所有的构造方法进行排序,参数个数多的在前面
- 遍历每个构造方法
- 如果不是调用getBean方法时所指定的构造方法参数值,那么则根据构造方法参数类型找值
- 如果是调用getBean方法时所指定的构造方法参数值,就直接利用这些值
- 如果根据当前构造方法找到了对应的构造方法参数值,那么这个构造方法就是可用的,但是不一定这个构造方法就是最佳的,所以这里会涉及到是否有多个构造方法匹配了同样的值, 这个时候就会用值和构造方法类型进行匹配程度的打分,找到一个最匹配的
分越少优先级越高
通过计算找到的bean和构造方法参数类型匹配程度有多高
假设A类型继承自B,B继承自C,同时A实现了接口D
如果构造方法的参数类型为 A,那么完全匹配,得分为0
如果构造方法的参数类型为B,那么得分为2
如果构造方法的参数类型为 C,那么得分为4
如果构造方法的参数类型为D,那么得分为1
// 测试代码
Object[] objects = new Object[]{new A()};
// 0
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{A.class}, objects));
// 2
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{B.class}, objects));
// 4
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{C.class}, objects));
// 1
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{D.class}, objects));
@Bean的情况
首先,Spring会把@Bean修饰的方法解析成BeanDefinition
:
-
如果方法是static的,那么解析出来的
BeanDefinition
中:factoryBeanName
为AppConfig
所对应的beanName,比如"appConfig"factoryMethodName
为对应的方法名,比如"aService"factoryClass
为AppConfig.class
-
如果方法不是static的,那么解析出来的
BeanDefinition
中:-
factoryBeanName
为null -
factoryMethodName
为对应的方法名,比如"aService" -
factoryClass
也为AppConfig.class
-
在由@Bean生成的BeanDefinition
中,有一个重要的属性isFactoryMethodUnique
,表示 factoryMethod
是不是唯一的,在普通情况下@Bean生成的BeanDefinition
的 isFactoryMethodUnique
为true,但是如果出现了方法重载,那么就是特殊的情况,比如:
@Bean
public static AService aService(){
return new AService();
}
@Bean
public AService aService(BService bService){
return new AService();
}
虽然有两个@Bean,但是肯定只会生成一个aService的Bean,那么Spring在处理@Bean时,也只会生成一个aService的BeanDefinition,比如Spring先解析到第一个@Bean,会生成一个 BeanDefinition,此时isFactoryMethodUnique为true,但是解析到第二个@Bean时,会判断出来 beanDefinitionMap
中已经存在一个aService的BeanDefinition了,那么会把之前的这个 BeanDefinition的isFactoryMethodUnique
修改为false,并且不会生成新的BeanDefinition了。 并且后续在根据BeanDefinition创建Bean时,会根据isFactoryMethodUnique
来操作,如果为 true,那就表示当前BeanDefinition只对应了一个方法,那也就是只能用这个方法来创建Bean了, 但是如果isFactoryMethodUnique为false,那就表示当前BeanDefition
对应了多个方法,需要和推断构造方法的逻辑一样,去选择用哪个方法来创建Bean.