99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)

大多数人只是停留在对 Spring 的简单应用上,所以一般也不会了解到 Spring 的构造方法注入。

其实在 Spring 的官网中明确说到:

  • Spring 推荐对于那些必须依赖注入的属性,使用构造方法注入;
  • 而那些不一定非要注入的属性,Spring 则推荐使用 setter 注入。

所以,既然 Spring 官网都那么说了,你要是连构造方法注入都不好好学习,那可就有点对不起自己啦。
99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第1张图片
既然要学习 Spring 的构造方法注入,我们先不研究源码,我们先从基本的使用开始:

我先给出一个 A 类,里面有各种各样的构造方法。
然后,你可以自己先思考,Spring 会调用哪个构造方法。
然后,看我的测试,看看你想的和 Spring 想的,到底一样不一样!

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
	public A(String str) {
     
		System.out.println("str");
	}
	public A(B b, C c) {
     
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
     
		System.out.println("B,C,str");
	}
}

我要往 Spring 容器中添加一个 B 一个 C,这样才能够给构造方法传参:

@Component
public class B {
     
}
@Component
public class C {
     
}

这里我使用注解驱动,初始化 Spring 容器:

public static void main(String[] args) {
     
	ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第2张图片
不知道你猜对了没有,即使猜错了也不要紧,我会慢慢分析的;
不过,假设你侥幸猜对了,那也不要高兴,看看能不能接住面试官的连环轰炸!

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第3张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第4张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第5张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(String str) {
     
		System.out.println("str");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第6张图片

@Component
public class A {
     
	public A(B b) {
     
		System.out.println("B");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第7张图片

@Component
public class A {
     
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第8张图片

@Component
public class A {
     
	public A(B b) {
     
		System.out.println("B");
	}
	public A(String str) {
     
		System.out.println("str");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第9张图片
想来你慢慢地已经开始自己发现规律了。
现在,我继续增加新的条件:

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	// 增加@Autowired注解
	@Autowired
	public A(C c) {
     
		System.out.println("C");
	}
	public A(String str) {
     
		System.out.println("str");
	}
	public A(B b, C c) {
     
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
     
		System.out.println("B,C,str");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第10张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
	public A(String str) {
     
		System.out.println("str");
	}
	// 增加@Autowired注解
	@Autowired
	public A(B b, C c) {
     
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
     
		System.out.println("B,C,str");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第11张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
	// 增加@Autowired注解
	@Autowired
	public A(String str) {
     
		System.out.println("str");
	}
	public A(B b, C c) {
     
		System.out.println("B,C");
	}
	public A(B b, C c, String str) {
     
		System.out.println("B,C,str");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第12张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	@Autowired
	public A(B b) {
     
		System.out.println("B");
	}
	@Autowired
	public A(C c) {
     
		System.out.println("C");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第13张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	@Autowired(required = false)
	public A(B b) {
     
		System.out.println("B");
	}
	@Autowired
	public A(C c) {
     
		System.out.println("C");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第14张图片

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	@Autowired(required = false)
	public A(B b) {
     
		System.out.println("B");
	}
	@Autowired(required = false)
	public A(C c) {
     
		System.out.println("C");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第15张图片

@Component
public class A {
     
	@Autowired(required = false)
	public A() {
     
		System.out.println("无参");
	}
	@Autowired(required = false)
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第16张图片
是不是发现,一开始也没有想到?
当然,现在你也应该可以开始慢慢找到规律了。

不过,你以为这就结束了吗?
面试官一定会给你继续连环轰炸!

现在,新增一个类D:

public class D implements I {
     
}
public interface I {
     
}

下面,给 xml 配置文件中加入如下配置,也就是给 a 设置基于构造方法的自动注入。

<bean id="a" class="com.jiang.bean.A" autowire="constructor">
</bean>

<bean id="b" class="com.jiang.bean.B">
</bean>

<bean id="c" class="com.jiang.bean.C">
</bean>

<bean id="d" class="com.jiang.bean.D">
</bean>

然后依据 xml 配置初始化容器:

public static void main(String[] args) {
     
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
}

下面继续进行判断:

public class A {
     
	@Autowired
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
	public A(D d) {
     
		System.out.println("D");
	}
	public A(B b, C c) {
     
		System.out.println("B,C");
	}
	public A(B b, C c, D d) {
     
		System.out.println("B,C,D");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第17张图片

public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第18张图片

public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第19张图片

public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(C c) {
     
		System.out.println("C");
	}
	public A(D d) {
     
		System.out.println("D");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第20张图片

public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(C c) {
     
		System.out.println("C");
	}
	public A(I i) {
     
		System.out.println("I");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第21张图片

public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(D d) {
     
		System.out.println("D");
	}
	public A(I i) {
     
		System.out.println("I");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第22张图片
是不是如果我不给你提,你打死也想不到,Spring 光一个构造方法,就有这么多的条件要去判断,可以衍生出这么多的题给你做!!!

看到这里,我想你也差不多可以自己总结出一些规律。

  • 就是在没有设置自动注入为构造方法注入的时候,会默认使用无参构造方法;
  • 如果没有无参构造方法,那就会使用唯一的那个构造方法;
  • 如果提供了不止一个构造方法,但没有无参构造方法,Spring 就会迷茫,所以会抛出异常;
  • 如果加了 @Autowired 注解,就会指定那个构造方法;
  • 如果,加了多个 @Autowired 注解,除非全都是 required=false,否则也会抛异常;
  • 加了多个 @Autowired 注解并且全部是 required=false 的话,那就会从这些构造方法中选择一个最合适的;
  • 如果是构造方法自动注入,那么就会从多个构造方法中,选出一个最合适的。

但是,如果用 @Autowired 指定了多个,或者开启了构造方法的自动注入,那么选出的最合适的又是哪个?
根据现象来看:

  • 首先,是参数个数最多的(当然得除去有无效参数的构造方法,比如没有往容器中添加的字符串等待);
  • 如果参数个数一样,那么就选参数实现了接口的;
  • 如果参数直接就是接口,那么优先级没有类级别高;
  • 如果都一样了,那么按照字符串的排列顺序,a、b、c…选出最前的。

说了这么多,不过,这些都是现象对不对,我还没有给你们分析源码。
那么,接下来,我们就要先找到 Spring 推断构造方法的代码。

既然什么时候会推断构造方法呢?
那肯定是要创建对象的时候,要去推断构造方法(比较构造方法是用来创建对象的)。
于是,我们在 Spring 中找到 createBeanInstance(beanName, mbd, args); 这个方法。

点进去,我们可以看到:
99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第23张图片
在 determineConstructorsFromBeanPostProcessors 这个方法处,返回了一个 构造方法数组;
而且,这个方法的名字,翻译成中文,就叫决定构造方法。

那么,很有可能,就是在这个方法中,推断出了要使用的构造方法。
于是,我们 debug 一下:
此时采用注解驱动开发,且 A 的代码为:

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	public A(B b, C c) {
     
		System.out.println("B,C");
	}
}

然后,开始 debug:
99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第24张图片
你会发现,它返回了一个 null。

是不是觉得很奇怪,怎么它推断不出构造方法吗?
按照道理,此时没有使用自动构造方法注入,那应该返回一个无参构造方法才对,可是怎么只有一个 null?

于是,我让你们抱着怀疑的心态,再试一试:
这回,只有一个无参构造方法了,Spring 总能推断的出来吧。

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第25张图片
总感觉不太对劲是吧,于是,再让你抱着怀疑的心态,再给你测试测试:

@Component
public class A {
     
	public A(B b) {
     
		System.out.println("B");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第26张图片
然后,你就会发现 Spring 把构造方法找出来了。
你肯能有很多问号,不过没关系,我再给你加一个问号:

@Component
public class A {
     
	public A() {
     
		System.out.println("无参");
	}
	public A(B b) {
     
		System.out.println("B");
	}
	@Autowired
	public A(B b, C c) {
     
		System.out.println("B,C");
	}
	public A(B b, C c, D d) {
     
		System.out.println("B,C,D");
	}
}

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)_第27张图片
你会发现,这次 Spring 又把构造方法找出来了,而且就是 @Autowired 指定的那个构造方法。

现在也许你很疑惑,不过不要担心,我来告诉你答案。
既然要探究 Spring 的做法,那我们就来分析源码:

---- 想了想,这次还是用源码+注释的形式吧,不会阅读代码的小伙伴,就背我的结论,面试的时候就跟面试官直接喷,说 Spring 源码是这么这么写的。。。。
---- 我给的细节很全,只要你记住了,一般不会碰到一些搞不定的面试题(我只是指推断构造方法相关的)。

我先解释一下,为什么上面的推断构造方法,给出的返回值为 null。
因为,如果最后没有找出构造方法的话,Spring 就会使用默认无参构造方法。
所以,返回 null,就表示,要使用无参构造方法,Spring 会在后面的代码进行调用无参构造方法进行创建对象。

注意!!!:这只是在没有开启构造方法自动注入的情况下!!!
假设开启了构造方法的自动注入,在这里返回 null 的时候,还会接着继续找构造方法。

---- 如果你直接看源码,可能比较费力,我先给你描述一遍流程,你再阅读源码,就会轻松很多。
---- 所有和 Kotlin 有关的代码都不去管,我没学过 Kotlin,也不用它做开发。

  • 首先是 lookup 的检查,这个不是这一篇文章的重点,所以我的代码不会贴这段
    (毕竟和推断构造方法没关系)
  • 先从缓存中获取,如果之前已经解析过构造方法了,那么此时就不用再解析,直接使用之前解析过的构造方法即可(一般只有原型的 bean 会创建多次,所以原型的 bean 会利用到缓存)。
  • 先拿到所有的构造方法
  • 遍历所有的构造方法
  • 获取构造方法的 @Autowired 的封装对象
  • 如果多个 @Autowired 都是 required=true,就会抛异常
  • 如果构造方法有 @Autowired ,就会存入 List
  • 如果有 @Autowired 注解,并且 required=true,那么就会返回这一个构造方法
  • 如果有 @Autowired 注解,并且都是 required=false,那么就会返回这些构造方法和一个无参构造方法的数组
  • 如果没有 @Autowired 注解,只有一个有参构造方法,就会会这一个构造方法
  • 如果都不是,就会返回 null

下面,我们就来看推断构造方法的源代码:
(虽然我写了很多注释,不过如果发现看不下去了,那至少背上面的流程,和背结论)

@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
		throws BeanCreationException {
     

	// Let's check for lookup methods here...
	// lookup相关我直接省略

	// Quick check on the concurrent map first, with minimal locking.
	// 这里是用于缓存,如果推断过构造方法了,那么就会保存在一个Map中,
	// 那么下一次来创建对象的时候,就直接返回Map中保存的,就不用再次推断构造方法了。
	// 从Map中取
	Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
	// 如果Map中取出的为null,说明没有创建过对象,此时要推断构造方法
	if (candidateConstructors == null) {
     
		// Fully synchronized resolution now...
		// 加锁保证线程安全(双检锁)
		synchronized (this.candidateConstructorsCache) {
     
			candidateConstructors = this.candidateConstructorsCache.get(beanClass);
			if (candidateConstructors == null) {
     
				Constructor<?>[] rawCandidates;
				try {
     
					// 先拿到所有的构造方法
					rawCandidates = beanClass.getDeclaredConstructors();
				}
				catch (Throwable ex) {
     
					抛异常
				}
				// 这个list用于存放加了@Autowired注解的构造方法
				List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
				// 加了@Autowired注解的构造方法,如果required==true,就用这个变量存放
				Constructor<?> requiredConstructor = null;
				// 用于存放默认无参构造方法
				Constructor<?> defaultConstructor = null;
				// 这个primaryConstructor是用来返回Kotlin类的主要构造方法
				// 但是如果是Java类,则只会返回null
				// 和Kotlin相关,所以不研究
				Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
				// 不考虑合成方法,此处忽略
				int nonSyntheticConstructors = 0;
				// 遍历所有的构造方法
				for (Constructor<?> candidate : rawCandidates) {
     
					// 如果是合成的构造方法(不要管这个)
					if (!candidate.isSynthetic()) {
     
						nonSyntheticConstructors++;
					}
					// 因为用的是Java,所以这个primaryConstructor一定为null,所以不用管
					else if (primaryConstructor != null) {
     
						continue;
					}
					// 如果构造方法加了@Autowired,就会获取到ann
					AnnotationAttributes ann = findAutowiredAnnotation(candidate);
					if (ann == null) {
     
						Class<?> userClass = ClassUtils.getUserClass(beanClass);
						if (userClass != beanClass) {
     
							try {
     
							    // 因为,如果有继承关系,那么父类可能会加@Autowired注解
							    // 所以尝试获取父类的构造方法
								Constructor<?> superCtor =
										userClass.getDeclaredConstructor(candidate.getParameterTypes());
								// 从父类那里寻找是否有添加@Autowired注解
								ann = findAutowiredAnnotation(superCtor);
							}
							catch (NoSuchMethodException ex) {
     
								// Simply proceed, no equivalent superclass constructor found...
							}
						}
					}
					// ann != null,说明这个构造方法加了@Autowired注解
					// 根据抛异常的规则,如果有多个构造方法加了@Autowired注解,
					// 只要有required=true,就要抛异常。
					// 所以如果加了多个注解,那么所有的注解都要required=false,才不会抛异常
					if (ann != null) {
     
						// requiredConstructor != null,
						// 说明之前也有构造方法加了@Autowired注解,并且required=true,那就要抛异常
						if (requiredConstructor != null) {
     
							抛异常
						}
						boolean required = determineRequiredStatus(ann);
						if (required) {
     
							// 如果不为空,那么就说明之前有构造方法加了@Autowired注解,就要抛异常
							if (!candidates.isEmpty()) {
     
								抛异常
							}
							// 这时,这个requiredConstructor就会被赋值
							requiredConstructor = candidate;
						}
						// 那么这个加了@Autowired注解的构造方法就会存入candidates这个list
						candidates.add(candidate);
					}
					// 如果参数是0,那么就是无参构造方法,就赋值给defaultConstructor
					else if (candidate.getParameterCount() == 0) {
     
					    // defaultConstructor就是在这里被找出赋值
						defaultConstructor = candidate;
					}
				}
				// 以为上面找出有@Autowired注解的构造方法都被添加到了candidates这个List中
				// 所以此时不为空,说明有加了@Autowired注解的构造方法
				if (!candidates.isEmpty()) {
     
					// Add default constructor to list of optional constructors, as fallback.
					// 如果@Autowired注解全是required=false,且额外有无参构造方法,就再加一个无参构造方法,
					// 所以candidates就一定有多个构造方法
					// 反之,要是required=true,那就不会进if
					// 那candidates中就只会有那一个确定的构造方法
					if (requiredConstructor == null) {
     
						if (defaultConstructor != null) {
     
							candidates.add(defaultConstructor);
						}
						else if (candidates.size() == 1 && logger.isInfoEnabled()) {
     
							打印日志
						}
					}
					// List转化为数组
					candidateConstructors = candidates.toArray(new Constructor<?>[0]);
				}
				// 上面完成了所有构造方法的判断
				// 如果只有一个构造方法,并且参数>0
				// 就是只有一个有参构造方法
				else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
     
					// 返回的数组,就是那一个构造方法
					candidateConstructors = new Constructor<?>[] {
     rawCandidates[0]};
				}
				// 这里由于不牵扯kotlin,所以primaryConstructor一定为null,所以不用考虑
				else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&
						defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {
     
					candidateConstructors = new Constructor<?>[] {
     primaryConstructor, defaultConstructor};
				}
				else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {
     
					candidateConstructors = new Constructor<?>[] {
     primaryConstructor};
				}
				// 有一个无参构造方法,或者有多个构造方法
				// candidateConstructors就会是长度为0的数组
				// 所以在只有无参构造方法的时候,会返回null,
				// 如果有多个构造方法,只要没加@Autowired注解,也返回null
				else {
     
					candidateConstructors = new Constructor<?>[0];
				}
				// 最后加入缓存
				this.candidateConstructorsCache.put(beanClass, candidateConstructors);
			}
		}
	}
	// 如果数组长度为0,就返回空
	// 如果长度不为0,说明有@Autowired注解的方法,
	// 或者也可能是只有一个有参构造方法
	return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

这就是 Spring 第一次推断构造方法,不过只是一个普通的,不考虑自动注入,配置参数的推断!
来看总结的返回结果:

  • 如果为空,说明有多个构造方法,或者只有一个默认构造方法
  • 如果不为空,那就可能就是只有一个有参构造方法,那么就会执行这个构造方法
  • 也可能只有一个 @Autowired 注解,并且 required=true
  • 也有可能返回了多个构造方法,说明有构造方法加了 @Autowired 注解,并且全部都是 required=false

不过,由于第一次推断,是不考虑自动注入,配置信息的。
所以,还可能有第二次的判断,最终决定。

// 这里第一次推断了构造方法
// 如果为空,说明有多个构造方法,或者只有一个默认构造方法
// 如果不为空,那就可能就是只有一个有参构造方法,那么就会执行这个构造方法
// 也可能只有一个@Autowired注解,并且required=true
// 也有可能返回了多个构造方法,说明有构造方法加了@Autowired注解,并且全部都是required=false
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 如果不为空的话,就会进入if来进行对象的创建
// 不过如果为空,但是自动注入模型为构造方法自动注入,也会调用方法再次决定构造方法去执行
// 或者,如果配置中指定了构造方法的参数,那么也会进入该方法
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
		mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
     
	return autowireConstructor(beanName, mbd, ctors, args);
}

那么,我们就要接着研究,autowireConstructor 这个方法。

return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);

可以发现,这个方法,就是调用了另一个方法,我们继续点进去:

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
		@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
     
	// 初始化bw,不要用管
	BeanWrapperImpl bw = new BeanWrapperImpl();
	this.beanFactory.initBeanWrapper(bw);

	// 最后被确定要使用的构造方法
	Constructor<?> constructorToUse = null;
	// 存放构造方法使用的参数
	ArgumentsHolder argsHolderToUse = null;
	// 最后被确定使用的参数
	Object[] argsToUse = null;

	// explicitArgs通过getBean方法传入,如果getBean方法调用的时候指定方法参数那么直接使用
	// 一般只有原型会手动getBean,可以指定传入参数。
	// 而单例bean在容器刷新时就有了,所以getBean传入参数没什么作用,不会再来创建
	// 所以,这里就是null,我们继续往后看
	if (explicitArgs != null) {
     
		argsToUse = explicitArgs;
	}
	else {
     
		// 如果在getBean方法没有指定则尝试从配置文件中解析,xml文件可以指定构造方法的参数
		Object[] argsToResolve = null;
		//尝试从缓存中获取
		synchronized (mbd.constructorArgumentLock) {
     
			// 已经解析过的构造方法或工厂方法,一般只有原型才会不为null,第一次创建解析,以后就不用再解析了
			constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
			if (constructorToUse != null && mbd.constructorArgumentsResolved) {
     
				// Found a cached constructor...
				// 直接使用之前解析过的参数
				argsToUse = mbd.resolvedConstructorArguments;
				if (argsToUse == null) {
     
					// 配置的构造函数参数(因为配置的参数value都是String,所以需要解析)
					argsToResolve = mbd.preparedConstructorArguments;
				}
			}
		}
		if (argsToResolve != null) {
     
			// 解析参数类型
			argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
		}
	}

	// 说明没有用getBean传参,也没有在配置文件中配置
	// 那么就会在后面开始选择构造方法开始执行
	if (constructorToUse == null || argsToUse == null) {
     
		// Take specified constructors, if any.
		// 这里是之前第一次选出的构造方法数组
		Constructor<?>[] candidates = chosenCtors;
		// ==null说明之前无法决定构造方法,说明需要构造方法自动注入模型来推断构造方法
		if (candidates == null) {
     
			Class<?> beanClass = mbd.getBeanClass();
			try {
     
				// 如果能访问非public的构造方法,那就获取所有的包括非public的构造方法,
				// 否则,只获取public的构造方法
				candidates = (mbd.isNonPublicAccessAllowed() ?
						beanClass.getDeclaredConstructors() : beanClass.getConstructors());
			}
			catch (Throwable ex) {
     
				抛异常
			}
		}
		// candidates中只有一个构造方法,说明可能之前已经选出了构造方法
		// 说明可能只有一个有参构造方法,或者有一个@Autowired指定了构造方法
		// 也可能是只有一个默认无参构造方法,在上面被获取到
		if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
     
			// 获取这一个构造方法
			Constructor<?> uniqueCandidate = candidates[0];
			// 如果是无参构造方法
			if (uniqueCandidate.getParameterCount() == 0) {
     
				// 保存解析过的构造方法和参数,用于缓存,以后创建对象就不用再解析
				synchronized (mbd.constructorArgumentLock) {
     
					mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
					mbd.constructorArgumentsResolved = true;
					mbd.resolvedConstructorArguments = EMPTY_ARGS;
				}
				// 因为是只有默认无参构造方法,所以可以返回了
				bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
				return bw;
			}
		}

		// Need to resolve the constructor.
		// 之前已经选出了构造方法,
		// 或者没选出但是指定了构造方法自动注入
		boolean autowiring = (chosenCtors != null ||
				mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
		ConstructorArgumentValues resolvedValues = null;
		
		// 参数最小要有多少个
		int minNrOfArgs;
		// 我们一般不会调用getBean传参创建时使用
		if (explicitArgs != null) {
     
			minNrOfArgs = explicitArgs.length;
		}
		else {
     
			// 提取配置文件中的配置的构造函数参数
			ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
			// 用于承载解析后的构造函数参数的值
			resolvedValues = new ConstructorArgumentValues();
			// 需要的最小参数值,配置了的参数的个数
			// 因为配置了那么多参数了,总不能还调用一个参数比配置的参数个数少的构造方法吧
			minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
		}

		// 排序给定的构造函数,public在前,非public在后
		// 按照构造方法的优先级进行排序
		// 由于这是Spring弄得优先级很复杂,不适合阅读,不过我们通过之前的测试代码,也发现了规律
		AutowireUtils.sortConstructors(candidates);
		// 这个变量用于记录最小的差异权重
		int minTypeDiffWeight = Integer.MAX_VALUE;
		// 如果有权重相等,那么放到这个模棱两可的集合中
		Set<Constructor<?>> ambiguousConstructors = null;
		// 发生异常不抛出,先存入这个List中
		LinkedList<UnsatisfiedDependencyException> causes = null;

		// 这里就会按照刚才的优先级顺序进行遍历
		for (Constructor<?> candidate : candidates) {
     
			Class<?>[] paramTypes = candidate.getParameterTypes();

			// 如果之前选用的构造方的的参数的个数 > 当前构造方法的参数个数,就break终止循环
			// 因为排序有顺序的,参数个数多的排在最前面
			// 所以,一般只要构造方法符合要求,Spring会自动选用最长的构造方法
			if (constructorToUse != null && argsToUse != null && argsToUse.length > paramTypes.length) {
     
				// Already found greedy constructor that can be satisfied ->
				// do not look any further, there are only less greedy constructors left.
				break;
			}
			if (paramTypes.length < minNrOfArgs) {
     
				// 构造方法参数个数还没有达到最小参数个数要求,
				// 因为至得和配置的参数个数一样多
				continue;
			}

			ArgumentsHolder argsHolder;
			// 如果封装的参数对象不为空
			if (resolvedValues != null) {
     
				try {
     
					String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
					if (paramNames == null) {
     
						ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
						if (pnd != null) {
     
							// 获取参数名称
							paramNames = pnd.getParameterNames(candidate);
						}
					}
					// 获取需要的参数值
					argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
							getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
				}
				catch (UnsatisfiedDependencyException ex) {
     
					if (logger.isTraceEnabled()) {
     
						logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
					}
					// Swallow and try next constructor.
					if (causes == null) {
     
						causes = new LinkedList<>();
					}
					// 将异常存入List中
					causes.add(ex);
					continue;
				}
			}
			else {
     
				// Explicit arguments given -> arguments length must match exactly.
				if (paramTypes.length != explicitArgs.length) {
     
					continue;
				}
				argsHolder = new ArgumentsHolder(explicitArgs);
			}

			// 获取差异权重值
			int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
					argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
			// Choose this constructor if it represents the closest match.
			// 如果它的类型差异权重更小,就选用这个构造方法和参数,
			// 然后把当前最小的类型差异权重设置为它的权重值
			if (typeDiffWeight < minTypeDiffWeight) {
     
				constructorToUse = candidate;
				argsHolderToUse = argsHolder;
				argsToUse = argsHolder.arguments;
				minTypeDiffWeight = typeDiffWeight;
				ambiguousConstructors = null;
			}
			// 如果它的类型差异权重和之前的一个最小的类型差异权重值相等
			// 就把这个构造方法添加到一个模棱两可构造方法set中,表示这些构造方法无法根据权重选出最优
			else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
     
				if (ambiguousConstructors == null) {
     
					ambiguousConstructors = new LinkedHashSet<>();
					ambiguousConstructors.add(constructorToUse);
				}
				// 加入模棱两可集合
				ambiguousConstructors.add(candidate);
			}
		}

		// 完成循环遍历后
		// 如果都没找到要用的构造方法,那就得抛异常
		if (constructorToUse == null) {
     
			if (causes != null) {
     
				UnsatisfiedDependencyException ex = causes.removeLast();
				for (Exception cause : causes) {
     
					this.beanFactory.onSuppressedException(cause);
				}
				throw ex;
			}
			抛异常
		}
		// 如果模棱两可构造方法集合不为空,并且不是宽松解析构造方法的话,就要抛异常
		// (一般默认是宽松的,所以遇到模棱两可直接使用排在前面的)
		else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
     
			抛异常
		}
		
		// 都不为空,说明已经解析到了要使用的构造方法
		if (explicitArgs == null && argsHolderToUse != null) {
     
			// 将解析的构造函数加入缓存,下次再创建就不用再解析了
			argsHolderToUse.storeCache(mbd, constructorToUse);
		}
	}

	Assert.state(argsToUse != null, "Unresolved constructor arguments");
	bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
	return bw;
}

源码就这么多,可以说确实没有什么难度,我对第二次的方法做一个小小的总结:

  • 如果之前解析过,缓存中有,就直接使用之前解析过的
    (一般只有原型 bean 会创建多次,才会利用到缓存)
  • 如果之前的推断的构造法只有一个,那就会直接使用这一个,然后 return
  • 如果之前推断出了构造方法不止一个,那么就会遍历这些构造方法,来进行挑选
  • 否则,就会遍历所有的构造方法
  • 如果在 getBean 传参,就会设置一个最小的参数个数要求,构造方法的个数必须比传参的个数多
  • 或者,如果配置了参数,则也会要求构造方法最小的参数个数要是配置的参数个数
  • 在遍历之前,会先进行排序,public 会排在前面,参数个数多的会排在前面
    (这也是 Spring 构造方法自动注入,会默认选择最长的构造方法的原因)
  • 遍历的时候,如果获取参数值抛异常,会先被 catch 起来,不抛出,等到最后遍历结束了,如果没有找到要用的构造方法,才会抛出异常
  • 遍历的时候,会计算这个构造方法的差异权重,差异权重最小的,会被选择上
  • 遍历的时候,如果发现遍历的这一个构造方法的参数比之前遍历选择小,就break终止遍历
    (这也是 Spring 构造方法自动注入,会默认选择最长的构造方法的原因,因为找到短的就不再遍历了,就只用之前长的)
  • 如果有两个构造方法,差异权重一样,那么就会存到一个集合中,表示模棱两可,不知道选谁好
  • 遍历结束后,如果没有找到要使用的构造方法,就会抛异常
  • 遍历结束后,如果发现模棱两可集合中有值,说明存在模棱两可的构造方法,如果解析构造函数是宽松要求,那就使用排序的前一个;如果不宽松要求,非常严谨,那么就会抛异常。
  • 解析的结果最后会加入缓存

构造方法的选择,如果你阅读到了这里,那你一定会收益匪浅。

很多时候,由于开发者只写了几行代码,就把 Spring 跑起来了,就认为已经学会了。
其实 Spring 在背后还默默地做了很多很多事。

所以,对于框架的学习,绝对不能只稍稍使用,我们还应该,多去阅读源码,理清其中的实现思路,以及提供给使用者的扩展点。
这样,才能更好地,对框架进行使用和扩展。

当然,Spring 由于除了构造方法注入,还会有基于 setter 的注入,关于自动注入,还有很多人抱有误解。
所以,笔者也建议你再去看一下我的这篇有关自动注入的文章,相信你会获益匪浅!
99%的人把Spring的自动注入理解错了!(范例→源码证明)

当然,最重要的,先把我提到的知识点,背过!!!

你可能感兴趣的:(Spring,spring,java,面试)