欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-01-29 13:14:58
...

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

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

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

所以,既然 Spring 官网都那么说了,你要是连构造方法注入都不好好学习,那可就有点对不起自己啦。
99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)
既然要学习 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推断构造方法!打破你三观!!!(范例→源码)
不知道你猜对了没有,即使猜错了也不要紧,我会慢慢分析的;
不过,假设你侥幸猜对了,那也不要高兴,看看能不能接住面试官的连环轰炸!

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

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

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

99%的人答不对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");
	}
}

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

@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推断构造方法!打破你三观!!!(范例→源码)

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

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

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

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

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

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

@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推断构造方法!打破你三观!!!(范例→源码)

@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推断构造方法!打破你三观!!!(范例→源码)

@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推断构造方法!打破你三观!!!(范例→源码)

@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推断构造方法!打破你三观!!!(范例→源码)

@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推断构造方法!打破你三观!!!(范例→源码)

@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推断构造方法!打破你三观!!!(范例→源码)

@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推断构造方法!打破你三观!!!(范例→源码)
是不是发现,一开始也没有想到?
当然,现在你也应该可以开始慢慢找到规律了。

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

现在,新增一个类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推断构造方法!打破你三观!!!(范例→源码)

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

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

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推断构造方法!打破你三观!!!(范例→源码)

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推断构造方法!打破你三观!!!(范例→源码)

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推断构造方法!打破你三观!!!(范例→源码)

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推断构造方法!打破你三观!!!(范例→源码)
是不是如果我不给你提,你打死也想不到,Spring 光一个构造方法,就有这么多的条件要去判断,可以衍生出这么多的题给你做!!!

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

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

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

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

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

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

点进去,我们可以看到:
99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)
在 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推断构造方法!打破你三观!!!(范例→源码)
你会发现,它返回了一个 null。

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

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

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

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

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

99%的人答不对Spring推断构造方法!打破你三观!!!(范例→源码)
然后,你就会发现 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推断构造方法!打破你三观!!!(范例→源码)
你会发现,这次 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