springBean的实例化过程详解---createBeanInstance(2)

回到createBeanInstance

		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			// 从ctors中自动选择一个构造方法
			return autowireConstructor(beanName, mbd, ctors, args);
		}

进行构造方法的推断需要一些条件
1.选择的可能用到的构造方法不为null
2.自动注入模型为 构造方法注入
3.BeanDefinition 里面保存了参数值
4.传入的args不为null(前文介绍过)《springBean的实例化过程详解—createBeanInstance(1)中介绍过》

这里可以先简单的介绍一下,如果没有进入这个构造方法推断怎么办

		return instantiateBean(beanName, mbd);

在方法的最后,就会直接进行实例化,这里面会拿到Bean的空参构造,然后反射创建

autowireConstructor

		// 得到构造方法的参数,
		if (explicitArgs != null) {
			// getBean()时传递了args参数时才进这里
			argsToUse = explicitArgs;
		}
		else {
			// 查看BeanDefinition中是否已经确定出来了所要使用的构造方法和构造方法的参数值(起到缓存的作用)
			Object[] argsToResolve = null;
			synchronized (mbd.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;

				if (constructorToUse != null && mbd.constructorArgumentsResolved) {
					argsToUse = mbd.resolvedConstructorArguments;
					if (argsToUse == null) {
						argsToResolve = mbd.preparedConstructorArguments;
					}
				}
			}
			if (argsToResolve != null) {
				argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
			}
		}

explicitArgs :用户传入的参数(也就是args),也就是说如果用户传了参数会优先使用
如果用户没有传参数,就会 BeanDefinition 的缓存里面找,如果是第一次进入这里,那么缓存里肯定是没有的

接下来是一个判断

	if (constructorToUse == null || argsToUse == null) 

这个判断的意思是,需要实例化的构造方法没找到,或者需要实例化的函数没找到,机会进入接下来的逻辑(寻找构造方法或者函数)
通俗点说,也就是当前还不具备去实例化的这个bean的一些条件

spring 是如何推断构造方法,并且找到注入的参数的
第 1 步

			Constructor<?>[] candidates = chosenCtors;
			//如果传入的构造方法为null
			if (candidates == null) {
				Class<?> beanClass = mbd.getBeanClass();
				try {
				//获取所有的构造方法
					candidates = (mbd.isNonPublicAccessAllowed() ?
							beanClass.getDeclaredConstructors() : beanClass.getConstructors());
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Resolution of declared constructors on bean Class [" + beanClass.getName() +
							"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
				}
			}

如果传入的构造方法数组为null,就会获取所有的构造方法,这里是有为null的可能的,因为进入这里,只需要满足外面 if 四个中的一个就可以

第 2 步

			// 如果只有一个构造方法,并且没有指定构造方法参数值,并且是无参构造方法
			if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
				Constructor<?> uniqueCandidate = candidates[0];
				// 并且是无参的构造方法,则可以直接使用该无参构造方法进行实例化了
				// 同时把该构造方法缓存到BeanDefinition中
				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;
				}
			}

这种情况简单概括就是,现在没有候选的参数,并且候选的构造函数只有一个空参的,那么当然是可以直接进行实例化的

			//自动注入模型是不是通过构造方法注入
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			ConstructorArgumentValues resolvedValues = null; // 记录解析后的构造方法参数值

			int minNrOfArgs; // 表示最少的构造方法的参数个数,要么是用户指定的构造方法参数,要么是BeanDefinition中所指定的
	
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			}
			else {
				// 从BeanDefinition中获取所设置的构造方法参数值
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
				// 记录解析后的构造方法参数值
				resolvedValues = new ConstructorArgumentValues();
				// 解析BeanDefinition中所设置的构造方法参数值
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}
AutowireUtils.sortConstructors(candidates);

这里比较关键的是 minNrOfArgs ,这个的意思是 构造方法最少的参数个数,举个例子,如果我们在
getBean的时候给其传两个参数,那么最小参数个数那一定就是 2 了,那么如果这个Bean 没有提供一个>=2的构造方法,那么就会报错,因为没有可用的构造方法
然后会将候选构造方法进行排序,并且是参数最多的放在前面

排完序后,就会对这些构造方法进行遍历,找到合适的构造方法

		for (Constructor<?> candidate : candidates) {
				// 当前构造方法的参数个数
				int parameterCount = candidate.getParameterCount();
				// constructorToUse表示所指定的或找到的构造方法
				// argsToUse表示所指定的或找到的构造方法参数值
				// 如果构造方法也找到了,构造方法参数值也确定出来了,并且找到的构造方法参数值的个数大于当前遍历到的构造方法参数值,则推出循				环,表示确定了构造方法
				// 如果是参数个数相等的话,则继续判断当前构造方法是不是比之前的那个更合适
				if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
					break;
				}
				// 如果构造方法参数小于minNrOfArgs,
				if (parameterCount < minNrOfArgs) {
					continue;
				}
				..............

过滤参数值不合格的构造方法

				// 如果不是调用getBean方法时所指定的构造方法参数值,那么则根据构造方法参数类型找值
				if (resolvedValues != null) {
					try {
						// 获取参数名
						// 查看是否在构造方法上使用@ConstructorProperties注解来定义构造方法参数的名字
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
						if (paramNames == null) {
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
							//将参数的名字找出来封装成一个数组
								paramNames = pnd.getParameterNames(candidate);
							}
						}
						// 根据当前构造方法的参数类型和参数名从beanFactory中得到bean作为参数值
						// resolvedValues表示所指定的构造方法参数值
						// paramTypes,当前构造方法的参数类型列表
						// paramNames,当前构造方法的参数值列表
						// getUserDeclaredConstructor(candidate)获取父类中被重写的构造方法
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
					}
					catch (UnsatisfiedDependencyException ex) {
						// 如果找不到对应的bean,也不会直接报错,只能证明当前遍历到的构造方法不能用
						if (logger.isTraceEnabled()) {
							logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
						}
						// Swallow and try next constructor.
						if (causes == null) {
							causes = new LinkedList<>();
						}
						causes.add(ex);
						continue;
					}
				}
				else {
					// 如果getBean提供的参数个数不等于当前构造方法的参数个数,则当前构造方法不合适
					if (parameterCount != explicitArgs.length) {
						continue;
					}
					argsHolder = new ArgumentsHolder(explicitArgs);
				}

resolvedValues != null:这个意思就是说当前getBean方法没有指定参数值,当然大多数情况是没有指定的
我们在前面的代码看到了一个constructorToUse 这个属性,这个属性如果是不为null ,就说明找到了合适的构造方法,会直接break循环
但是一段代码其实没有对这个变量操作,因为这里只是找到了参数个数可以使用的,但是不代表参数类型也完美匹配,所以还会进行后续推断,并且这里将构造方法参数的名字找出来了,然后封装成一个数组也是为了后续的判断

// 根据参数类型计算权重
				int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
				// 取参数类型权重较小的构造方法
				if (typeDiffWeight < minTypeDiffWeight) {
					constructorToUse = candidate;
					argsHolderToUse = argsHolder;
					argsToUse = argsHolder.arguments;
					minTypeDiffWeight = typeDiffWeight;
					ambiguousConstructors = null;
				}
				else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
					// 如果权重一样,则记录在ambiguousConstructors中
					if (ambiguousConstructors == null) {
						ambiguousConstructors = new LinkedHashSet<>();
						ambiguousConstructors.add(constructorToUse);
					}
					ambiguousConstructors.add(candidate);
				}

这一部分代码是用来计算权重的,spring选取最适合的构造方法的办法就是通过计算权重,从代码上可以看出来
typeDiffWeight < minTypeDiffWeight,也就是说,当前前构造方法的权重比之前的权重小,才会做替换操作,所以权重越小,这个构造方法越是合适
仔细看一下这段代码

int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) :argsHolder.getAssignabilityWeight(paramTypes));

这是个三木运算,这里的mbd.isLenientConstructorResolution()就像是一个开关,这个开关默认是true,
其实再计算构造方法的权重的时候,也就是去判断构造方法的参数类型 与 根据构造方法参数类型从spring容器中获取的对象的匹配程度
举个例子 aaa 是个顶级父类 ,bbb 是 aaa 的子类 ,ccc 是 bbb 的子类
那么 aaa 与 bbb 的权重就是1,aaa与ccc,权重为2 ,aaa 与 aaa 权重为0,如果有多个参数就会将多个参数的权重加起来,通过这种方式,就可以得到最佳的构造方法了

回到刚才的问题,argsHolder.getTypeDifferenceWeight(paramTypes) :argsHolder.getAssignabilityWeight(paramTypes) 有什么区别
区别就是一个允许权重不为0就进行构造,但是另外一个不允许,也就是说当mbd.isLenientConstructorResolution()为false的时候,权重必须为0,才算是一个合格的构造方法,除此之外,都不算是合格的构造方法

			// 遍历完所有构造方法后,没有找到合适的构造方法,则报错
			if (constructorToUse == null) {
				if (causes != null) {
					UnsatisfiedDependencyException ex = causes.removeLast();
					for (Exception cause : causes) {
						this.beanFactory.onSuppressedException(cause);
					}
					throw ex;
				}
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Could not resolve matching constructor " +
						"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
			}

那么如果一个构造都不匹配怎么办?
那么就会在接下来的判断中直接报错,如果匹配成功,就会反射创建对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值