createBean如何寻找构造器(四)------类型转换与匹配权重
代码仓库 :Gitee 仓库链接
本文档的所有示例代码都可以在代码仓库中找到,建议结合代码一起阅读。
⚠️ 重要提醒
请大家学习这一段代码时,保持平静。 其实本人已经破防了,这部分代码有点。。。
Spring 的类型转换和匹配权重计算涉及复杂的算法和大量的代码,学习过程中可能会遇到:
- 类型转换的多种策略
- 匹配权重计算的复杂逻辑
- 类型差异权重的计算规则
- 多个构造器之间的比较和选择
这些都是正常的。请保持耐心,采用抽丝剥茧、层层深入的方式,一步一步理解,不要急于求成。
1. 回顾上一章节
在上一篇文章《createBean如何寻找构造器(三)------多参数构造器的匹配》中,我们深入分析了多参数构造器的参数解析机制,包括:
resolveConstructorArguments方法的完整流程:从 Bean 定义中提取参数值,解析为实际对象resolveValueIfNecessary方法的能力:处理 Bean 引用、内部 Bean、集合类型、类型化值等- 配置与封装类型的对应关系:XML 配置如何转换为 Spring 内部的封装类型
- 设计思想的体现:职责分离、统一处理、递归解析等设计原则
现在,我们将继续深入探索 类型转换与匹配权重,重点关注:
createArgumentArray方法的详细实现:如何将解析后的参数值转换为构造器参数类型- 类型转换的详细过程 :
TypeConverter如何工作,类型差异如何计算 - 匹配权重的计算逻辑:当存在多个构造器时,如何计算匹配权重并选择最佳构造器
ArgumentsHolder的作用:如何保存转换后的参数值和类型转换结果
2. 统一术语
在深入源码之前,让我们先明确本文涉及的关键术语:
| 术语 | 定义 |
|---|---|
| ArgumentsHolder | Spring 内部用于保存构造器参数值和类型转换结果的容器 |
| 参数类型转换(Type Conversion) | Spring 将配置的参数值(如字符串)转换为构造器参数类型(如 Integer)的过程 |
| 匹配权重(Matching Weight) | Spring 用于衡量构造器匹配程度的数值,权重越低表示匹配度越高 |
| 类型差异权重(Type Difference Weight) | 用于计算参数类型与目标类型差异的权重值 |
| 参数名称发现器(ParameterNameDiscoverer) | Spring 用于获取方法/构造器参数名称的工具接口 |
| TypeConverter | Spring 的类型转换器接口,负责将值从一种类型转换为另一种类型 |
| BeanWrapper | Spring 的 Bean 包装器,提供了类型转换和属性访问的能力 |
3. 问题场景
在上一篇文章中,我们已经理解了 Spring 如何解析构造器参数值(从 Bean 定义中提取并解析为实际对象)。但是,解析后的参数值还需要转换为构造器参数的实际类型,才能用于构造器调用。
💡 问题的复杂性:
当 Bean 类存在多个构造器时,Spring 需要解决以下问题:
- 参数类型转换 :配置的参数值(如 XML 中的字符串
"25")需要转换为构造器参数的实际类型(如Integer) - 类型差异评估:评估参数类型与目标类型的差异程度
- 匹配权重计算:当多个构造器都满足条件时,需要计算匹配权重,选择最佳匹配
- 构造器选择:根据匹配权重选择权重最低(匹配度最高)的构造器
3.1 核心问题
本文将通过源码探索,回答以下核心问题:
-
✅
createArgumentArray方法是如何将解析后的参数值转换为构造器参数类型的?(本文已解答)- 参数值如何从
ConstructorArgumentValues中提取? - 类型转换是如何进行的?
TypeConverter是如何工作的?
- 参数值如何从
-
✅ 类型差异权重是如何计算的?(本文已解答)
- 类型差异权重的计算规则是什么?
- 为什么权重越低表示匹配度越高?
- 不同类型之间的差异如何量化?
-
✅ 当存在多个构造器时,Spring 如何计算匹配权重并选择最佳构造器?(本文已解答)
- 匹配权重的计算规则是什么?
- 如何比较多个构造器的匹配权重?
- 选择策略是什么?
-
✅
ArgumentsHolder的作用是什么?它如何保存转换后的参数值?(本文已解答)ArgumentsHolder的数据结构是什么?- 它如何缓存转换结果?
- 缓存机制如何提高性能?
-
✅ 候选构造器是如何排序和遍历的?(本文已解答)
- 构造器排序规则是什么?
- 遍历过程中有哪些优化策略?
- 参数名称是如何获取的?
4. 测试用例准备
为了深入理解类型转换和匹配权重的计算机制,我们需要使用上一篇文章中创建的两个测试用例:
MultiArgBean:用于展示参数类型转换的详细过程AmbiguousConstructorBean:用于展示多个构造器都满足条件时的匹配权重计算
4.1 测试用例一:MultiArgBean(类型转换场景)
这个测试用例在上一篇文章中已经创建,用于展示类型转换的详细过程。
4.1.1 Bean 类定义
java
package com.example.constructor;
/**
* 多参数构造器的Bean类
* 用于演示Spring如何从多个构造器中选择最合适的构造器
*
* 本示例重点展示:
* 1. 参数类型转换(字符串转Integer、Long等)
* 2. 多个构造器都满足条件时的选择策略
* 3. 匹配权重的计算逻辑
*/
public class MultiArgBean {
// ... (代码见上一篇文章)
}
4.1.2 XML 配置文件
xml
<bean id="multiArgBean" class="com.example.constructor.MultiArgBean">
<constructor-arg index="0" value="测试名称"/>
<constructor-arg index="1" value="25"/>
<constructor-arg index="2" value="测试描述"/>
</bean>
💡 关键观察点:
- XML 配置的第二个参数是字符串
"25",Spring 需要将其转换为Integer类型 - 通过调试
createArgumentArray方法,可以观察类型转换的详细过程 - 可以观察
ArgumentsHolder如何保存转换后的参数值
4.2 测试用例二:AmbiguousConstructorBean(匹配权重场景)
这个测试用例在上一篇文章中已经创建,用于展示匹配权重的计算和选择逻辑。
4.2.1 Bean 类定义
java
package com.example.constructor;
/**
* 构造器歧义的Bean类
* 用于演示当多个构造器都满足条件时,Spring如何计算匹配权重并选择最佳构造器
*/
public class AmbiguousConstructorBean {
// ... (代码见上一篇文章)
}
4.2.2 XML 配置文件
xml
<bean id="ambiguousBean" class="com.example.constructor.AmbiguousConstructorBean">
<constructor-arg index="0" value="测试名称"/>
<constructor-arg index="1" value="100"/>
</bean>
💡 关键观察点:
- XML 配置的第二个参数是字符串
"100" - 存在三个双参数构造器都满足条件:
AmbiguousConstructorBean(String, Integer)- 需要将"100"转换为IntegerAmbiguousConstructorBean(String, Long)- 需要将"100"转换为LongAmbiguousConstructorBean(String, String)- 无需转换,精确匹配String
- Spring 需要计算每个构造器的匹配权重,选择权重最低(匹配度最高)的构造器
- 通过调试可以观察:
createArgumentArray方法如何为每个构造器创建参数数组- 类型转换的过程(String → Integer/Long)
- 匹配权重的计算(精确匹配权重更低)
- 最终选择的构造器及其权重值
5. 源码探索
提示 :本节将记录作者在调试过程中的源码探索过程,采用抽丝剥茧、层层深入的方式,逐步揭示 Spring 类型转换和匹配权重计算的完整机制。
5.1 调试入口
在测试用例中设置断点,开始调试。由于我们在上一篇文章中已经熟悉了 resolveConstructorArguments 方法的参数解析机制,这次我们继续看 ConstructorResolver 类的 autowireConstructor 方法,从 AutowireUtils.sortConstructors(candidates) 这里开始看。
💡 关键观察点:这次的关键点在于理解 Spring 如何将解析后的参数值转换为构造器参数类型,以及如何计算匹配权重。
5.2 候选构造器的排序和遍历
在 autowireConstructor 方法中,当获取到候选构造器数组后,Spring 会先对构造器进行排序,然后遍历排序后的构造器,寻找最合适的构造器。
5.2.1 构造器排序
java
AutowireUtils.sortConstructors(candidates);
🔍 排序规则:
Spring 使用 AutowireUtils.sortConstructors 方法对候选构造器进行排序,排序规则如下:
- public 构造器优先:public 构造器排在非 public 构造器前面
- 参数多的优先:在相同可见性的情况下,参数数量多的构造器排在前面
💡 设计思想:
- public 优先:public 构造器是公开的 API,优先使用更符合面向对象的设计原则
- 参数多的优先:参数多的构造器通常包含更多的信息,可能更精确地匹配需求
5.2.2 遍历候选构造器
排序完成后,Spring 开始遍历排序后的候选构造器:
java
for (Constructor<?> candidate : candidates) {
// 如果已经找到合适的构造器,并且构造器的参数个数 >= 当前构造器参数个数,则直接结束
if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
// 已经找到合适的构造器,结束遍历
break;
}
// 继续处理当前候选构造器...
}
🔍 关键逻辑:
- 提前结束条件 :如果已经找到合适的构造器(
constructorToUse != null),并且该构造器的参数个数大于当前候选构造器的参数个数(argsToUse.length > parameterCount),则直接结束遍历 - 优化性能:由于构造器已经按参数数量从多到少排序,如果已经找到参数更多的构造器,就不需要再检查参数更少的构造器了
5.2.3 获取构造器参数名称
在遍历候选构造器时,Spring 需要获取构造器的参数名称,用于后续的参数匹配:
java
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
String[] paramNames = null;
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
}
🔍 关键点:
Spring 通过 ParameterNameDiscoverer 接口获取构造器的参数名称。这里我们重点关注 ParameterNameDiscoverer 的两个实现类:
-
StandardReflectionParameterNameDiscoverer:- 主要用于 JDK 1.8+,能够通过反射机制从构造器直接获取参数名称
- JDK 1.8 之前获取到的是
arg0、arg1等占位符名称 - JDK 1.8+ 如果编译时使用了
-parameters参数,可以获取到真实的参数名称
-
LocalVariableTableParameterNameDiscoverer:- 通过解析字节码中的 LocalVariableTable 来获取参数名称
- 适用于 JDK 1.8 之前或编译时没有使用
-parameters参数的情况 - 需要类文件包含调试信息(LocalVariableTable),如果编译时使用了
-g参数,可以获取到参数名称 - 如果类文件不包含调试信息,则无法获取参数名称,返回
null
💡 关键理解:
- 参数名称的作用 :参数名称用于后续的参数匹配,特别是当使用
<constructor-arg name="paramName" value="..."/>这种配置方式时 - JDK 版本差异 :JDK 1.8+ 支持通过反射获取参数名称,但需要编译时添加
-parameters参数 - 降级策略:如果无法获取参数名称,Spring 会使用索引或类型匹配的方式
- 两种实现的选择 :Spring 通常会组合使用这两种实现,先尝试
StandardReflectionParameterNameDiscoverer,如果失败则尝试LocalVariableTableParameterNameDiscoverer
5.2.4 构造器参数匹配的核心逻辑
获取参数名称后,Spring 需要将 Bean 定义中的参数值与构造器参数进行匹配。这个过程涉及以下几个关键步骤:
-
判断构造器参数数量是否满足要求:
javaint parameterCount = candidate.getParameterCount(); if (parameterCount < minNrOfArgs) { // 参数数量不足,跳过该构造器 continue; } -
创建参数数组 :调用
createArgumentArray方法,将 Bean 定义中的参数值转换为构造器参数类型 -
计算匹配权重:如果多个构造器都满足条件,需要计算匹配权重,选择最佳匹配
-
选择最佳构造器:根据匹配权重选择权重最低(匹配度最高)的构造器
5.3 createArgumentArray 方法详解
createArgumentArray 方法是构造器参数匹配的核心方法,它负责将 Bean 定义中的参数值转换为构造器参数的实际类型。
5.3.1 方法签名和基本流程
java
private Object[] createArgumentArray(
String beanName, RootBeanDefinition mbd,
ConstructorArgumentValues resolvedValues,
BeanWrapper bw, Class<?>[] paramTypes,
String[] paramNames, ParameterNameDiscoverer pnd) {
// 1. 创建 ArgumentsHolder 对象
// 2. 遍历构造器参数,为每个参数找到对应的值
// 3. 进行类型转换
// 4. 返回转换后的参数数组
}
🔍 方法参数说明:
beanName:Bean 名称mbd:合并后的 Bean 定义resolvedValues:已解析的构造器参数值(来自resolveConstructorArguments方法)bw:BeanWrapper 对象,用于类型转换paramTypes:构造器参数类型数组paramNames:构造器参数名称数组(可能为 null)pnd:参数名称发现器
5.3.2 ArgumentsHolder 的创建
java
ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
🔍 ArgumentsHolder 的作用:
ArgumentsHolder 是 Spring 内部用于保存构造器参数值和类型转换结果的容器,它包含:
arguments:转换后的参数值数组rawArguments:原始参数值数组(未转换)types:参数类型数组convertible:标记是否可以进行类型转换
💡 设计思想:
- 缓存转换结果 :
ArgumentsHolder可以缓存类型转换的结果,避免重复转换 - 保存原始值:同时保存原始值和转换后的值,便于调试和错误处理
5.3.3 参数值的匹配和提取
对于每个构造器参数,Spring 需要从 resolvedValues 中找到对应的值:
java
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
Class<?> paramType = paramTypes[paramIndex];
String paramName = (paramNames != null ? paramNames[paramIndex] : null);
// 1. 尝试通过索引获取参数值
ConstructorArgumentValues.ValueHolder valueHolder =
resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
// 2. 如果通过索引获取不到,尝试通过类型和名称匹配
if (valueHolder == null) {
valueHolder = resolvedValues.getArgumentValue(paramType, paramName, usedValueHolders);
}
// 3. 如果还是获取不到,尝试通过类型匹配(忽略名称)
if (valueHolder == null) {
valueHolder = resolvedValues.getArgumentValue(paramType, null, usedValueHolders);
}
// 4. 如果仍然获取不到,使用默认值或抛出异常
if (valueHolder == null) {
// 处理默认值或抛出异常
}
}
🔍 匹配策略:
Spring 按照以下优先级匹配参数值:
- 索引匹配 :如果配置中指定了
index属性,优先通过索引匹配 - 类型 + 名称匹配 :如果配置中指定了
type和name属性,通过类型和名称匹配 - 类型匹配 :如果只指定了
type属性,通过类型匹配 - 顺序匹配:如果都没有指定,按照 XML 中的顺序匹配
💡 关键理解:
usedValueHolders的作用 :用于记录已经使用过的ValueHolder,避免同一个参数值被多个参数使用- 匹配的灵活性:Spring 支持多种匹配方式,提供了很大的灵活性
5.4 类型转换的详细过程
当找到对应的参数值后,Spring 需要进行类型转换,将参数值转换为构造器参数的实际类型。
5.4.1 类型转换的入口
java
Object convertedValue = valueHolder.getValue();
if (convertedValue != null) {
// 如果参数值已经是目标类型,无需转换
if (paramType.isInstance(convertedValue)) {
args.arguments[paramIndex] = convertedValue;
args.rawArguments[paramIndex] = convertedValue;
} else {
// 需要进行类型转换
convertedValue = convertToTypedValue(convertedValue, paramType);
args.arguments[paramIndex] = convertedValue;
args.rawArguments[paramIndex] = valueHolder.getValue();
}
}
5.4.2 TypeConverter 的使用
Spring 使用 TypeConverter 接口进行类型转换,默认实现是 BeanWrapperImpl:
java
Object convertedValue = this.beanFactory.getTypeConverter()
.convertIfNecessary(value, paramType, new MethodParameter(candidate, paramIndex));
🔍 类型转换的过程:
- 检查是否需要转换:如果参数值已经是目标类型,直接返回
- 查找转换器:根据源类型和目标类型查找合适的转换器
- 执行转换:调用转换器进行类型转换
- 处理转换异常 :如果转换失败,抛出
TypeMismatchException
5.4.3 类型转换的示例
以 MultiArgBean 为例,XML 配置中的 "25" 需要转换为 Integer 类型:
xml
<constructor-arg index="1" value="25"/>
转换过程:
- 原始值:
String "25" - 目标类型:
Integer - 转换器:Spring 内置的
StringToNumberConverter - 转换结果:
Integer 25
5.5 类型差异权重的计算
当多个构造器都满足条件时,Spring 需要计算类型差异权重,以选择最佳匹配的构造器。
5.5.1 类型差异权重的概念
类型差异权重用于量化参数类型与目标类型的差异程度:
- 权重越低:表示类型差异越小,匹配度越高
- 权重越高:表示类型差异越大,匹配度越低
5.5.2 类型差异权重的计算规则
Spring 使用 AutowireUtils.getTypeDifferenceWeight 方法计算类型差异权重:
java
public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {
int result = 0;
for (int i = 0; i < paramTypes.length; i++) {
if (args[i] != null) {
Class<?> paramType = paramTypes[i];
Class<?> argType = args[i].getClass();
// 计算类型差异权重
int weight = getTypeDifferenceWeight(paramType, argType);
result += weight;
}
}
return result;
}
🔍 权重计算规则:
- 精确匹配 :如果参数类型与目标类型完全相同,权重为
0 - 父类匹配 :如果参数类型是目标类型的父类,权重为
2 - 接口实现 :如果参数类型实现了目标接口,权重为
4 - 类型转换 :如果需要类型转换(如 String → Integer),权重为
8 - 无法匹配 :如果无法匹配,权重为
Integer.MAX_VALUE
💡 示例:
对于 AmbiguousConstructorBean 的三个构造器:
AmbiguousConstructorBean(String, String):两个参数都是精确匹配,权重 = 0 + 0 = 0AmbiguousConstructorBean(String, Integer):第二个参数需要转换(String → Integer),权重 = 0 + 8 = 8AmbiguousConstructorBean(String, Long):第二个参数需要转换(String → Long),权重 = 0 + 8 = 8
因此,Spring 会选择 AmbiguousConstructorBean(String, String) 构造器。
5.6 匹配权重的计算逻辑
匹配权重是构造器选择的最终依据,它综合考虑了类型差异、参数数量等因素。
5.6.1 匹配权重的计算
java
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
AutowireUtils.getTypeDifferenceWeight(paramTypes, args.arguments) :
AutowireUtils.getTypeDifferenceWeight(paramTypes, args.arguments));
🔍 关键点:
isLenientConstructorResolution():如果为true,表示使用宽松的构造器解析策略- 类型差异权重 :通过
getTypeDifferenceWeight方法计算
5.6.2 最佳构造器的选择
java
if (typeDiffWeight < minTypeDiffWeight) {
constructorToUse = candidate;
argsHolderToUse = args;
argsToUse = args.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousConstructors = null;
}
🔍 选择策略:
- 选择权重最低的构造器 :
typeDiffWeight < minTypeDiffWeight - 处理歧义 :如果多个构造器的权重相同,记录到
ambiguousConstructors中,后续可能抛出异常
5.6.3 构造器歧义的处理
如果多个构造器的匹配权重相同,Spring 会记录这些构造器:
java
if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
if (ambiguousConstructors == null) {
ambiguousConstructors = new LinkedHashSet<>();
ambiguousConstructors.add(constructorToUse);
}
ambiguousConstructors.add(candidate);
}
如果最终存在歧义,Spring 会抛出 BeanCreationException:
java
if (ambiguousConstructors != null) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous constructor matches found in bean '" + beanName + "' " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
}
5.7 ArgumentsHolder 的作用详解
ArgumentsHolder 是 Spring 内部用于保存构造器参数值和类型转换结果的容器,它在构造器匹配过程中起到关键作用。
5.7.1 ArgumentsHolder 的数据结构
java
private static class ArgumentsHolder {
public final Object[] rawArguments; // 原始参数值(未转换)
public final Object[] arguments; // 转换后的参数值
public final Class<?>[] types; // 参数类型数组
public final boolean[] convertible; // 标记是否可以进行类型转换
}
5.7.2 ArgumentsHolder 的缓存机制
ArgumentsHolder 可以缓存类型转换的结果,避免重复转换:
java
// 如果 ArgumentsHolder 已经标记为可转换,直接使用缓存的结果
if (args.convertible) {
return args.arguments;
}
💡 性能优化:
- 避免重复转换:如果同一个构造器被多次匹配,可以复用转换结果
- 提高性能:类型转换可能涉及复杂的计算,缓存可以显著提高性能
5.7.3 ArgumentsHolder 的使用场景
ArgumentsHolder 在以下场景中被使用:
- 构造器匹配 :在
createArgumentArray方法中创建,用于保存转换后的参数值 - 构造器缓存 :如果构造器匹配成功,
ArgumentsHolder会被缓存到 Bean 定义中 - 构造器调用 :最终使用
ArgumentsHolder.arguments作为构造器的实际参数
5.4 类型差异权重的计算
(待作者继续调试后补充)
5.5 匹配权重的计算逻辑
(待作者继续调试后补充)
5.6 ArgumentsHolder 的作用
(待作者继续调试后补充)
6. 核心机制总结
通过本次源码探索,我们深入理解了 Spring 在构造器选择过程中的类型转换和匹配权重计算机制。以下是本文的核心发现:
6.1 构造器选择的完整流程
Spring 的构造器选择流程可以概括为以下几个步骤:
-
候选构造器排序:
- 使用
AutowireUtils.sortConstructors对候选构造器排序 - 排序规则:public 构造器优先,参数多的优先
- 使用
-
遍历候选构造器:
- 按排序后的顺序遍历候选构造器
- 如果已找到合适的构造器且参数更多,提前结束遍历
-
获取参数名称:
- 通过
ParameterNameDiscoverer获取构造器参数名称 - 支持两种实现:
StandardReflectionParameterNameDiscoverer和LocalVariableTableParameterNameDiscoverer
- 通过
-
创建参数数组:
- 调用
createArgumentArray方法,将 Bean 定义中的参数值转换为构造器参数类型 - 使用
ArgumentsHolder保存转换后的参数值
- 调用
-
计算匹配权重:
- 通过
getTypeDifferenceWeight方法计算类型差异权重 - 选择权重最低(匹配度最高)的构造器
- 通过
-
处理构造器歧义:
- 如果多个构造器的权重相同,记录到
ambiguousConstructors中 - 最终如果存在歧义,抛出
BeanCreationException
- 如果多个构造器的权重相同,记录到
6.2 createArgumentArray 方法的核心机制
createArgumentArray 方法是构造器参数匹配的核心,它完成了以下工作:
-
参数值匹配:
- 按照索引、类型+名称、类型、顺序的优先级匹配参数值
- 使用
usedValueHolders避免同一个参数值被多个参数使用
-
类型转换:
- 使用
TypeConverter接口进行类型转换 - 默认实现是
BeanWrapperImpl - 支持多种类型转换策略
- 使用
-
结果缓存:
- 使用
ArgumentsHolder保存转换后的参数值 - 可以缓存转换结果,避免重复转换
- 使用
6.3 类型差异权重的计算规则
类型差异权重用于量化参数类型与目标类型的差异程度:
| 匹配类型 | 权重值 | 说明 |
|---|---|---|
| 精确匹配 | 0 | 参数类型与目标类型完全相同 |
| 父类匹配 | 2 | 参数类型是目标类型的父类 |
| 接口实现 | 4 | 参数类型实现了目标接口 |
| 类型转换 | 8 | 需要进行类型转换(如 String → Integer) |
| 无法匹配 | Integer.MAX_VALUE | 无法匹配 |
💡 关键理解:
- 权重越低,匹配度越高:权重为 0 表示精确匹配,权重为 8 表示需要类型转换
- 选择策略:Spring 会选择权重最低的构造器,即匹配度最高的构造器
6.4 ArgumentsHolder 的作用和优势
ArgumentsHolder 是 Spring 内部用于保存构造器参数值和类型转换结果的容器:
-
数据结构:
rawArguments:原始参数值(未转换)arguments:转换后的参数值types:参数类型数组convertible:标记是否可以进行类型转换
-
缓存机制:
- 可以缓存类型转换的结果,避免重复转换
- 提高性能,减少不必要的计算
-
使用场景:
- 构造器匹配过程中保存转换后的参数值
- 构造器缓存时保存匹配结果
- 构造器调用时作为实际参数
6.5 设计思想总结
-
职责分离:
createArgumentArray负责参数匹配和类型转换getTypeDifferenceWeight负责计算类型差异权重ArgumentsHolder负责保存转换结果
-
性能优化:
- 构造器排序优化遍历顺序
- 提前结束遍历避免不必要的检查
ArgumentsHolder缓存转换结果
-
灵活性:
- 支持多种参数匹配方式(索引、类型、名称、顺序)
- 支持多种类型转换策略
- 支持宽松和严格的构造器解析策略
6.6 本文的局限性
本文主要聚焦于类型转换和匹配权重的计算机制,以下内容将在后续文章中展开:
DependencyDescriptor的处理:注解驱动的依赖注入如何处理- 更复杂的类型转换场景:泛型、集合类型等的转换
- 构造器缓存机制的详细分析:如何缓存构造器匹配结果
7. 思考题
7.1 本文已解答的问题
-
✅
createArgumentArray方法是如何将解析后的参数值转换为构造器参数类型的?- 本文详细分析了该方法的完整实现,包括参数值匹配、类型转换等
- 说明了参数值如何从
ConstructorArgumentValues中提取,以及类型转换的详细过程 - 介绍了
TypeConverter的使用方式和转换策略
-
✅ 类型差异权重是如何计算的?
- 本文详细说明了类型差异权重的计算规则
- 解释了为什么权重越低表示匹配度越高
- 提供了不同类型之间差异的量化标准(精确匹配 0,类型转换 8 等)
-
✅ 当存在多个构造器时,Spring 如何计算匹配权重并选择最佳构造器?
- 本文详细说明了匹配权重的计算规则和选择策略
- 介绍了如何比较多个构造器的匹配权重
- 说明了构造器歧义的处理方式
-
✅
ArgumentsHolder的作用是什么?它如何保存转换后的参数值?- 本文详细说明了
ArgumentsHolder的数据结构 - 介绍了缓存机制如何提高性能
- 说明了
ArgumentsHolder的使用场景
- 本文详细说明了
-
✅ 候选构造器是如何排序和遍历的?
- 本文详细说明了构造器排序规则(public 优先,参数多的优先)
- 介绍了遍历过程中的优化策略(提前结束遍历)
- 说明了参数名称获取的机制
7.2 后续文章将展开的问题
-
DependencyDescriptor的处理逻辑是什么?- 本文暂时跳过了这部分内容,将在后续文章单独创建一个例子来详细讲解
- 包括注解驱动的依赖注入如何处理
-
更复杂的类型转换场景
- 泛型类型的转换
- 集合类型的转换
- 嵌套类型的转换
-
构造器缓存机制的详细分析
- 如何缓存构造器匹配结果
- 缓存的生命周期
- 缓存的失效条件
8. 大白话总结
8.1 用一句话总结
本文深入探索了 Spring 在构造器选择过程中的类型转换和匹配权重计算机制,重点分析了 createArgumentArray 方法如何将 Bean 定义中的参数值转换为构造器参数类型,以及如何通过类型差异权重选择最佳匹配的构造器。
8.2 核心流程
想象一下,Spring 在选择构造器时,就像一个智能匹配系统:
-
排序候选构造器:
- 就像面试官先按学历和经验排序候选人
- public 构造器优先,参数多的优先
-
遍历候选构造器:
- 逐个检查每个构造器是否满足条件
- 如果已经找到更好的,就不需要再检查了
-
参数值匹配:
- 就像给每个职位找到最合适的候选人
- 按照索引、类型、名称等优先级匹配参数值
-
类型转换:
- 就像把候选人的技能转换成职位要求的能力
- 将配置的参数值(如字符串
"25")转换为构造器参数类型(如Integer)
-
计算匹配权重:
- 就像给每个候选人的匹配度打分
- 精确匹配得分最高(权重最低),需要转换的得分较低(权重较高)
-
选择最佳构造器:
- 就像选择匹配度最高的候选人
- 选择权重最低(匹配度最高)的构造器
8.3 关键发现
-
类型差异权重的量化:
- Spring 通过权重值量化类型差异
- 精确匹配权重为 0,需要转换的权重为 8
- 权重越低,匹配度越高
-
ArgumentsHolder 的缓存机制:
ArgumentsHolder可以缓存类型转换的结果- 避免重复转换,提高性能
-
灵活的匹配策略:
- 支持多种参数匹配方式(索引、类型、名称、顺序)
- 提供了很大的灵活性
8.4 本文的边界
本文主要聚焦于类型转换和匹配权重计算的机制,即如何将 Bean 定义中的参数值转换为构造器参数类型,以及如何通过类型差异权重选择最佳匹配的构造器。但是:
DependencyDescriptor的处理:注解驱动的依赖注入如何处理,将在后续文章展开- 更复杂的类型转换场景:泛型、集合类型等的转换,将在后续文章展开
9. 参考资料
9.1 相关源码类和方法
本文涉及的主要源码类和方法:
ConstructorResolver.createArgumentArray()- 创建构造器参数数组的核心方法(本文重点)TypeConverter.convertIfNecessary()- 类型转换的核心方法(本文重点)ArgumentsHolder- 构造器参数值容器(本文重点)TypeConverter- 类型转换器接口BeanWrapper- Bean 包装器,提供类型转换能力
9.2 相关文章
- createBean如何寻找构造器(一)------无参构造器的场景
- createBean如何寻找构造器(二)------单参数构造器的场景
- createBean如何寻找构造器(三)------多参数构造器的匹配
上一篇 :createBean如何寻找构造器(三)------多参数构造器的匹配
下一篇:[createBean如何寻找构造器(五)------DependencyDescriptor与注解驱动(待发布)]
10. 总结
本文深入探索了 Spring 在构造器选择过程中的类型转换和匹配权重计算机制,重点分析了 createArgumentArray 方法的完整实现。通过本次探索,我们理解了:
- 构造器选择的完整流程:从候选构造器排序到最终选择最佳匹配的构造器
- 参数值匹配的多种策略:索引、类型+名称、类型、顺序等多种匹配方式
- 类型转换的详细过程 :如何使用
TypeConverter进行类型转换 - 类型差异权重的计算规则:如何量化类型差异,选择最佳匹配
- ArgumentsHolder 的作用:如何保存转换结果,提高性能
虽然本文内容已经非常丰富,但构造器选择的完整机制还包括:
DependencyDescriptor的处理逻辑- 更复杂的类型转换场景(泛型、集合类型等)
- 构造器缓存机制的详细分析
这些内容将在后续文章中继续探索。让我们保持耐心,继续深入理解 Spring 的构造器选择机制!