前言

在前面的讲解中,我们了解了如何获取构造器。当只有一个符合条件的构造器时,自然会选择它作为初始化的构造器。然而,在上一节中,我们遇到了一种特殊情况:当有多个符合条件的构造器时,返回的是一个数组。在这种情况下,Spring又是如何从多个构造器中选择最合适的呢?今天,我们将讨论的主题是:autowireConstructor方法。

autowireConstructor

让我们首先深入研究一下该方法的主要源代码,毕竟源代码是最好的老师。

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) { BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw); Constructor<?> constructorToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null; // 如果getBean()传入了args,那构造方法要用的入参就直接确定好了
if (explicitArgs != null) {
argsToUse = explicitArgs;
}
else {
Object[] argsToResolve = null;
synchronized (mbd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
if (constructorToUse != null && mbd.constructorArgumentsResolved) {
// Found a cached constructor...
argsToUse = mbd.resolvedConstructorArguments;
if (argsToUse == null) {
argsToResolve = mbd.preparedConstructorArguments;
}
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
}
} // 如果没有确定要使用的构造方法,或者确定了构造方法但是所要传入的参数值没有确定
if (constructorToUse == null || argsToUse == null) { // Take specified constructors, if any.
// 如果没有指定构造方法,那就获取beanClass中的所有构造方法所谓候选者
Constructor<?>[] candidates = chosenCtors;
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);
}
} // 如果只有一个候选构造方法,并且没有指定所要使用的构造方法参数值,并且该构造方法是无参的,那就直接用这个无参构造方法进行实例化了
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; // 确定要选择的构造方法的参数个数的最小值,后续判断候选构造方法的参数个数如果小于minNrOfArgs,则直接pass掉
int minNrOfArgs;
if (explicitArgs != null) {
// 如果直接传了构造方法参数值,那么所用的构造方法的参数个数肯定不能少于
minNrOfArgs = explicitArgs.length;
}
else {
// 如果通过BeanDefinition传了构造方法参数值,因为有可能是通过下标指定了,比如0位置的值,2位置的值,虽然只指定了2个值,但是构造方法的参数个数至少得是3个
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
resolvedValues = new ConstructorArgumentValues();
// 处理RuntimeBeanReference
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
} // 对候选构造方法进行排序,public的方法排在最前面,都是public的情况下参数个数越多越靠前
AutowireUtils.sortConstructors(candidates);
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Constructor<?>> ambiguousConstructors = null;
Deque<UnsatisfiedDependencyException> causes = null; // 遍历每个构造方法,进行筛选
for (Constructor<?> candidate : candidates) {
// 参数个数
int parameterCount = candidate.getParameterCount(); // 本次遍历时,之前已经选出来了所要用的构造方法和入参对象,并且入参对象个数比当前遍历到的这个构造方法的参数个数多,则不用再遍历,退出循环
if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
// Already found greedy constructor that can be satisfied ->
// do not look any further, there are only less greedy constructors left.
break;
}
// 如果参数个数小于所要求的参数个数,则遍历下一个,这里考虑的是同时存在public和非public的构造方法
if (parameterCount < minNrOfArgs) {
continue;
} ArgumentsHolder argsHolder;
Class<?>[] paramTypes = candidate.getParameterTypes();
// 没有通过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);
}
} // 根据参数类型、参数名找到对应的bean对象
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 ArrayDeque<>(1);
}
causes.add(ex);
continue;
}
}
else {
// Explicit arguments given -> arguments length must match exactly.
// 在调getBean方法时传入了参数值,那就表示只能用对应参数个数的构造方法
if (parameterCount != explicitArgs.length) {
continue;
}
// 不用再去BeanFactory中查找bean对象了,已经有了,同时当前正在遍历的构造方法就是可用的构造方法
argsHolder = new ArgumentsHolder(explicitArgs);
} // 当前遍历的构造方法所需要的入参对象都找到了,根据参数类型和找到的参数对象计算出来一个匹配值,值越小越匹配
// Lenient表示宽松模式
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;
}
// 值相等的情况下,记录一下匹配值相同的构造方法
else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
if (ambiguousConstructors == null) {
ambiguousConstructors = new LinkedHashSet<>();
ambiguousConstructors.add(constructorToUse);
}
ambiguousConstructors.add(candidate);
}
}
// 遍历结束 x // 如果没有可用的构造方法,就取记录的最后一个异常并抛出
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 on bean class [" + mbd.getBeanClassName() + "] " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
}
// 如果有可用的构造方法,但是有多个
else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous constructor matches found on bean class [" + mbd.getBeanClassName() + "] " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
ambiguousConstructors);
} // 如果没有通过getBean方法传入参数,并且找到了构造方法以及要用的入参对象则缓存
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;
}

在进入这个方法之前,还存在一个缓存层,因为原型BeanDefinition可能会多次创建Bean,但无需每次都重新寻找构造器。因此,当第一次找到构造器时,会被缓存起来。如果缓存中已经存在构造方法,那么可以直接进行实例化,无需再次执行推断方法。如果在之前的步骤中没有找到其他构造器,那么将会使用无参构造器来实例化Bean。

推断方法判断

我们现在来仔细观察一下autowireConstructor方法的整体流程,这样我们可以更清楚地理解其运作方式。

如果没有明确确定要使用的构造方法,或者已确定构造方法但其所需传入参数值尚未确定。

  1. 当没有确定要使用的构造方法时,可以遍历类中的所有构造方法。
  2. 当类中只存在一个无参构造方法时,可以直接使用该无参构造方法进行实例化,无需额外的选择操作。
  3. 在选择构造方法时,需要确定所需参数个数的最小值。若已传入构造方法参数值,则所选构造方法的参数个数必不少于传入值的个数;若未传入参数值,则需检查BeanDefinition中是否指定了某个下标的值,确保最小值大于该下标。

比如这样配置:

public class UserServiceBeanPostProcessor implements BeanFactoryPostProcessor {

	@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition userService = beanFactory.getBeanDefinition("userService");
//这里我瞎写的null,正常应该是对象。
userService.getConstructorArgumentValues().addIndexedArgumentValue(1,null);
}
}
  1. 对候选构造方法进行排序。首先,将public修饰的构造方法排在最前面;若所有构造方法均为public,那么参数个数越多的构造方法越靠前。

  2. 遍历每个构造方法

  3. 在调用getBean()方法时,如果不指定构造方法的参数值,系统会根据构造器中的参数类型和参数名来匹配相应的bean对象。

  4. 在调用getBean()方法时,如果指定了构造方法的参数值,系统会直接利用这些参数值来实例化bean对象

  5. 在确定构造方法时,尽管找到了匹配的构造方法参数值,但并不意味着这个构造方法是最佳选择。因此,需要考虑是否存在多个构造方法匹配了相同的值。在这种情况下,系统将会根据值和构造方法类型之间的匹配程度进行评分,以找到最佳匹配的构造方法。

分值越低越匹配

打分规则是基于分值越低越匹配的原则。要确定分值,我们需要将代码提取出来进行运行,因为底层的逻辑相当复杂,需要仔细分析。

public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
// 最终值和类型的匹配程度
int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
// 原始值和类型的匹配程度,并减掉1024,使得原始值的匹配值更优先,意思就是优先根据原始值来算匹配值
int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
// 取最小值
return Math.min(rawTypeDiffWeight, typeDiffWeight);
}

那么,让我们来查看一下getTypeDifferenceWeight方法能够输出怎样的数值。

首先,我们定义一个A类,该类继承自B类,而B类又继承自C类,同时A类实现了接口D。

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));

通过仔细观察,我们可以明白为什么分值越低越具有匹配性。

总结

在本文中,我们深入研究了Spring框架中的autowireConstructor方法。该方法用于在存在多个构造器时选择最合适的构造器进行实例化Bean。通过分析源代码和推断方法判断的流程,我们了解到系统是如何根据参数个数、类型和数值的匹配程度来选择最佳构造器的。

在实际应用中,我们需要注意遍历构造方法、参数个数的最小值、排序规则、参数值的匹配等细节。

总的来说,autowireConstructor方法是Spring框架中一个关键的方法,它为我们提供了灵活且智能的构造器选择机制,帮助我们更好地管理Bean的实例化过程。通过学习和掌握这一方法,我们能够更好地运用Spring框架。

当未指定且存在多个构造器,实例化对象时Spring如何选择?的更多相关文章

  1. 一篇文章看懂java反射机制(反射实例化对象-反射获得构造方法,获得普通方法,获得字段属性)

    Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class对象传入一个包名+类名的字符串就可以得到C ...

  2. Java 子类实例化对象的过程

    子类实例化是否会实例化父类? 不会.父类在子类实例化过程中是并没有被实例化,java中new子类没有实例化父类,只是调用父类的构造方法初始化了,子类从父类继承来的属性,这个调用是子类的对象调用的父类的 ...

  3. 乐字节Java反射之二:实例化对象、接口与父类、修饰符和属性

    大家好,小乐继续接着上集:乐字节Java反射之一:反射概念与获取反射源头Class 这次是之二:实例化对象.接口与父类.修饰符和属性 一:实例化对象 之前我们讲解过创建对象的方式,有new .克隆.反 ...

  4. 转:java实例化对象的过程

    学习JAVA这门面向对象的语言,实质就是不断地创建类,并把类实例化为对象并调用方法.对于初学JAVA的人总搞清楚对象是如何实例化的,假如类之间存在继承关系,那就更糊涂了.下面我们通过两个例题来说明对象 ...

  5. 一个类的实例化对象所占空间的大小(对象大小= vptr(可能不止一个) + 所有非静态数据成员大小 + Aligin字节大小(依赖于不同的编译器))

    注意不要说类的大小,是类的对象的大小. 首先,类的大小是什么?确切的说,类只是一个类型定义,它是没有大小可言的. 用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小. 如果 Class ...

  6. 【面向对象】----【prototype&&__proto__&&实例化对象三者之间的关系】(四)-----【巷子】

    1.构造函数 a.什么是构造函数? 解释:通过关键字new 创建的函数叫做构造函数 作用:用来创建一个对象 废话少说直接上代码,首先我们还是创建一个构造函数人类 然后我们在创建两个实例,一个凡尘 一个 ...

  7. 【面向对象】【prototype&&__proto__&&实例化对象三者之间的关系】

    1.构造函数 a.什么是构造函数? 解释:通过关键字new 创建的函数叫做构造函数 作用:用来创建一个对象 废话少说直接上代码,首先我们还是创建一个构造函数人类 然后我们在创建两个实例,一个凡尘 一个 ...

  8. 面向对象---prototype、__proto__、实例化对象三者之间的关系

    1.构造函数 a.什么是构造函数? 解释:通过关键字new 创建的函数叫做构造函数 作用:用来创建一个对象 废话少说直接上代码,首先我们还是创建一个构造函数人类 然后我们在创建两个实例,一个凡尘 一个 ...

  9. [转]ThinkPHP中实例化对象M()和D()的区别,select和find的区别

    1.ThinkPHP中实例化对象M()和D()的区别 在实例化的过程中,经常使用D方法和M方法,这两个方法的区别在于M方法实例化模型无需用户为每个数据表定义模型类,如果D方法没有找到定义的模型类,则会 ...

  10. java实例化对象的方式

    一.Java中创建(实例化)对象的五种方式  1.用new语句直接创建对象,这是最常见的创建对象的方法. 2.通过工厂方法返回对象,如:String str = String.valueOf(23); ...

随机推荐

  1. 深入理解TF-IDF、BM25算法与BM25变种:揭秘信息检索的核心原理与应用

    深入理解TF-IDF.BM25算法与BM25变种:揭秘信息检索的核心原理与应用 1.文本特征表示方法: TF-IDF 在信息检索, 文本挖掘和自然语言处理领域, IF-IDF 这个名字, 从它在 20 ...

  2. 强化学习技巧四:模型训练速度过慢、GPU利用率较低,CPU利用率很低问题总结与分析。

    1.PyTorchGPU利用率较低问题原因: 在服务器端或者本地pc端, 输入nvidia-smi 来观察显卡的GPU内存占用率(Memory-Usage),显卡的GPU利用率(GPU-util),然 ...

  3. 有用的工具类(Java)

    IP地址获取 public class IPUtil { private static final String UNKNOWN = "unknown"; protected IP ...

  4. 在Spring Cloud 2020中使用Consul配置中心遇到的问题

    升级Spring Cloud 2020后发现Consul配置中心失效了,配置中心的配置和bootstrap.yml中的配置都没有生效. 话不多说,先看官方文档:https://docs.spring. ...

  5. Laravel日期处理

    1. 常用: echo Carbon::now(); // 2023-04-08 18:07:24 echo Carbon::today(); // 2023-04-08 00:00:00 echo ...

  6. 使用DoraCloud搭建支持统信UOS桌面的信创云桌面系统

    信创云桌面 信创云桌面采用国产的芯片,支持国产的桌面操作系统.本方案采用海光CPU的服务器,运行DoraCloud云桌面系统.可以支持统信UOS桌面系统和麒麟桌面操作系统. 环境准备 服务器:海光 5 ...

  7. electron 安装不同的版本的方法

    1.官网:http://www.electronjs.org/ 2.思考,既然是npm 安装,那么肯定也在 npm中央仓库有,那么去中央仓库看下: npm i -D electron@11.0.4

  8. 【scikit-learn基础】--模型持久化

    模型持久化(模型保存与加载)是机器学习完成的最后一步.因为,在实际情况中,训练一个模型可能会非常耗时,如果每次需要使用模型时都要重新训练,这无疑会浪费大量的计算资源和时间. 通过将训练好的模型持久化到 ...

  9. Windows 10 快捷键大全|日常办公效率加倍

    ## 复制.粘贴及其他常规     Ctrl + X 剪切选定项. Ctrl + C(或 Ctrl + Insert) 复制选定项. Ctrl + V(或 Shift + Insert) 粘贴选定项. ...

  10. MYSQL 3 DAY

    目录 MySQL day03 1.约束 1.1.唯一性约束(unique) 1.2.主键约束 1.3.外键约束 2.存储引擎?(整个内容属于了解内容) 2.1.完整的建表语句 2.2.什么是存储引擎呢 ...