【Spring源码】createBean如何寻找构造器(四)——类型转换与匹配权重

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 需要解决以下问题:

  1. 参数类型转换 :配置的参数值(如 XML 中的字符串 "25")需要转换为构造器参数的实际类型(如 Integer
  2. 类型差异评估:评估参数类型与目标类型的差异程度
  3. 匹配权重计算:当多个构造器都满足条件时,需要计算匹配权重,选择最佳匹配
  4. 构造器选择:根据匹配权重选择权重最低(匹配度最高)的构造器

3.1 核心问题

本文将通过源码探索,回答以下核心问题:

  1. createArgumentArray 方法是如何将解析后的参数值转换为构造器参数类型的?(本文已解答)

    • 参数值如何从 ConstructorArgumentValues 中提取?
    • 类型转换是如何进行的?
    • TypeConverter 是如何工作的?
  2. 类型差异权重是如何计算的?(本文已解答)

    • 类型差异权重的计算规则是什么?
    • 为什么权重越低表示匹配度越高?
    • 不同类型之间的差异如何量化?
  3. 当存在多个构造器时,Spring 如何计算匹配权重并选择最佳构造器?(本文已解答)

    • 匹配权重的计算规则是什么?
    • 如何比较多个构造器的匹配权重?
    • 选择策略是什么?
  4. ArgumentsHolder 的作用是什么?它如何保存转换后的参数值?(本文已解答)

    • ArgumentsHolder 的数据结构是什么?
    • 它如何缓存转换结果?
    • 缓存机制如何提高性能?
  5. 候选构造器是如何排序和遍历的?(本文已解答)

    • 构造器排序规则是什么?
    • 遍历过程中有哪些优化策略?
    • 参数名称是如何获取的?

4. 测试用例准备

为了深入理解类型转换和匹配权重的计算机制,我们需要使用上一篇文章中创建的两个测试用例:

  1. MultiArgBean:用于展示参数类型转换的详细过程
  2. 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" 转换为 Integer
    • AmbiguousConstructorBean(String, Long) - 需要将 "100" 转换为 Long
    • AmbiguousConstructorBean(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 方法对候选构造器进行排序,排序规则如下:

  1. public 构造器优先:public 构造器排在非 public 构造器前面
  2. 参数多的优先:在相同可见性的情况下,参数数量多的构造器排在前面

💡 设计思想

  • 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 的两个实现类:

  1. StandardReflectionParameterNameDiscoverer

    • 主要用于 JDK 1.8+,能够通过反射机制从构造器直接获取参数名称
    • JDK 1.8 之前获取到的是 arg0arg1 等占位符名称
    • JDK 1.8+ 如果编译时使用了 -parameters 参数,可以获取到真实的参数名称
  2. 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 定义中的参数值与构造器参数进行匹配。这个过程涉及以下几个关键步骤:

  1. 判断构造器参数数量是否满足要求

    java 复制代码
    int parameterCount = candidate.getParameterCount();
    if (parameterCount < minNrOfArgs) {
        // 参数数量不足,跳过该构造器
        continue;
    }
  2. 创建参数数组 :调用 createArgumentArray 方法,将 Bean 定义中的参数值转换为构造器参数类型

  3. 计算匹配权重:如果多个构造器都满足条件,需要计算匹配权重,选择最佳匹配

  4. 选择最佳构造器:根据匹配权重选择权重最低(匹配度最高)的构造器

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 按照以下优先级匹配参数值:

  1. 索引匹配 :如果配置中指定了 index 属性,优先通过索引匹配
  2. 类型 + 名称匹配 :如果配置中指定了 typename 属性,通过类型和名称匹配
  3. 类型匹配 :如果只指定了 type 属性,通过类型匹配
  4. 顺序匹配:如果都没有指定,按照 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));

🔍 类型转换的过程

  1. 检查是否需要转换:如果参数值已经是目标类型,直接返回
  2. 查找转换器:根据源类型和目标类型查找合适的转换器
  3. 执行转换:调用转换器进行类型转换
  4. 处理转换异常 :如果转换失败,抛出 TypeMismatchException
5.4.3 类型转换的示例

MultiArgBean 为例,XML 配置中的 "25" 需要转换为 Integer 类型:

xml 复制代码
<constructor-arg index="1" value="25"/>

转换过程:

  1. 原始值:String "25"
  2. 目标类型:Integer
  3. 转换器:Spring 内置的 StringToNumberConverter
  4. 转换结果: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;
}

🔍 权重计算规则

  1. 精确匹配 :如果参数类型与目标类型完全相同,权重为 0
  2. 父类匹配 :如果参数类型是目标类型的父类,权重为 2
  3. 接口实现 :如果参数类型实现了目标接口,权重为 4
  4. 类型转换 :如果需要类型转换(如 String → Integer),权重为 8
  5. 无法匹配 :如果无法匹配,权重为 Integer.MAX_VALUE

💡 示例

对于 AmbiguousConstructorBean 的三个构造器:

  • AmbiguousConstructorBean(String, String):两个参数都是精确匹配,权重 = 0 + 0 = 0
  • AmbiguousConstructorBean(String, Integer):第二个参数需要转换(String → Integer),权重 = 0 + 8 = 8
  • AmbiguousConstructorBean(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 在以下场景中被使用:

  1. 构造器匹配 :在 createArgumentArray 方法中创建,用于保存转换后的参数值
  2. 构造器缓存 :如果构造器匹配成功,ArgumentsHolder 会被缓存到 Bean 定义中
  3. 构造器调用 :最终使用 ArgumentsHolder.arguments 作为构造器的实际参数

5.4 类型差异权重的计算

(待作者继续调试后补充)

5.5 匹配权重的计算逻辑

(待作者继续调试后补充)

5.6 ArgumentsHolder 的作用

(待作者继续调试后补充)

6. 核心机制总结

通过本次源码探索,我们深入理解了 Spring 在构造器选择过程中的类型转换和匹配权重计算机制。以下是本文的核心发现:

6.1 构造器选择的完整流程

Spring 的构造器选择流程可以概括为以下几个步骤:

  1. 候选构造器排序

    • 使用 AutowireUtils.sortConstructors 对候选构造器排序
    • 排序规则:public 构造器优先,参数多的优先
  2. 遍历候选构造器

    • 按排序后的顺序遍历候选构造器
    • 如果已找到合适的构造器且参数更多,提前结束遍历
  3. 获取参数名称

    • 通过 ParameterNameDiscoverer 获取构造器参数名称
    • 支持两种实现:StandardReflectionParameterNameDiscovererLocalVariableTableParameterNameDiscoverer
  4. 创建参数数组

    • 调用 createArgumentArray 方法,将 Bean 定义中的参数值转换为构造器参数类型
    • 使用 ArgumentsHolder 保存转换后的参数值
  5. 计算匹配权重

    • 通过 getTypeDifferenceWeight 方法计算类型差异权重
    • 选择权重最低(匹配度最高)的构造器
  6. 处理构造器歧义

    • 如果多个构造器的权重相同,记录到 ambiguousConstructors
    • 最终如果存在歧义,抛出 BeanCreationException

6.2 createArgumentArray 方法的核心机制

createArgumentArray 方法是构造器参数匹配的核心,它完成了以下工作:

  1. 参数值匹配

    • 按照索引、类型+名称、类型、顺序的优先级匹配参数值
    • 使用 usedValueHolders 避免同一个参数值被多个参数使用
  2. 类型转换

    • 使用 TypeConverter 接口进行类型转换
    • 默认实现是 BeanWrapperImpl
    • 支持多种类型转换策略
  3. 结果缓存

    • 使用 ArgumentsHolder 保存转换后的参数值
    • 可以缓存转换结果,避免重复转换

6.3 类型差异权重的计算规则

类型差异权重用于量化参数类型与目标类型的差异程度:

匹配类型 权重值 说明
精确匹配 0 参数类型与目标类型完全相同
父类匹配 2 参数类型是目标类型的父类
接口实现 4 参数类型实现了目标接口
类型转换 8 需要进行类型转换(如 String → Integer)
无法匹配 Integer.MAX_VALUE 无法匹配

💡 关键理解

  • 权重越低,匹配度越高:权重为 0 表示精确匹配,权重为 8 表示需要类型转换
  • 选择策略:Spring 会选择权重最低的构造器,即匹配度最高的构造器

6.4 ArgumentsHolder 的作用和优势

ArgumentsHolder 是 Spring 内部用于保存构造器参数值和类型转换结果的容器:

  1. 数据结构

    • rawArguments:原始参数值(未转换)
    • arguments:转换后的参数值
    • types:参数类型数组
    • convertible:标记是否可以进行类型转换
  2. 缓存机制

    • 可以缓存类型转换的结果,避免重复转换
    • 提高性能,减少不必要的计算
  3. 使用场景

    • 构造器匹配过程中保存转换后的参数值
    • 构造器缓存时保存匹配结果
    • 构造器调用时作为实际参数

6.5 设计思想总结

  1. 职责分离

    • createArgumentArray 负责参数匹配和类型转换
    • getTypeDifferenceWeight 负责计算类型差异权重
    • ArgumentsHolder 负责保存转换结果
  2. 性能优化

    • 构造器排序优化遍历顺序
    • 提前结束遍历避免不必要的检查
    • ArgumentsHolder 缓存转换结果
  3. 灵活性

    • 支持多种参数匹配方式(索引、类型、名称、顺序)
    • 支持多种类型转换策略
    • 支持宽松和严格的构造器解析策略

6.6 本文的局限性

本文主要聚焦于类型转换和匹配权重的计算机制,以下内容将在后续文章中展开:

  • DependencyDescriptor 的处理:注解驱动的依赖注入如何处理
  • 更复杂的类型转换场景:泛型、集合类型等的转换
  • 构造器缓存机制的详细分析:如何缓存构造器匹配结果

7. 思考题

7.1 本文已解答的问题

  1. createArgumentArray 方法是如何将解析后的参数值转换为构造器参数类型的?

    • 本文详细分析了该方法的完整实现,包括参数值匹配、类型转换等
    • 说明了参数值如何从 ConstructorArgumentValues 中提取,以及类型转换的详细过程
    • 介绍了 TypeConverter 的使用方式和转换策略
  2. 类型差异权重是如何计算的?

    • 本文详细说明了类型差异权重的计算规则
    • 解释了为什么权重越低表示匹配度越高
    • 提供了不同类型之间差异的量化标准(精确匹配 0,类型转换 8 等)
  3. 当存在多个构造器时,Spring 如何计算匹配权重并选择最佳构造器?

    • 本文详细说明了匹配权重的计算规则和选择策略
    • 介绍了如何比较多个构造器的匹配权重
    • 说明了构造器歧义的处理方式
  4. ArgumentsHolder 的作用是什么?它如何保存转换后的参数值?

    • 本文详细说明了 ArgumentsHolder 的数据结构
    • 介绍了缓存机制如何提高性能
    • 说明了 ArgumentsHolder 的使用场景
  5. 候选构造器是如何排序和遍历的?

    • 本文详细说明了构造器排序规则(public 优先,参数多的优先)
    • 介绍了遍历过程中的优化策略(提前结束遍历)
    • 说明了参数名称获取的机制

7.2 后续文章将展开的问题

  1. DependencyDescriptor 的处理逻辑是什么?

    • 本文暂时跳过了这部分内容,将在后续文章单独创建一个例子来详细讲解
    • 包括注解驱动的依赖注入如何处理
  2. 更复杂的类型转换场景

    • 泛型类型的转换
    • 集合类型的转换
    • 嵌套类型的转换
  3. 构造器缓存机制的详细分析

    • 如何缓存构造器匹配结果
    • 缓存的生命周期
    • 缓存的失效条件

8. 大白话总结

8.1 用一句话总结

本文深入探索了 Spring 在构造器选择过程中的类型转换和匹配权重计算机制,重点分析了 createArgumentArray 方法如何将 Bean 定义中的参数值转换为构造器参数类型,以及如何通过类型差异权重选择最佳匹配的构造器。

8.2 核心流程

想象一下,Spring 在选择构造器时,就像一个智能匹配系统

  1. 排序候选构造器

    • 就像面试官先按学历和经验排序候选人
    • public 构造器优先,参数多的优先
  2. 遍历候选构造器

    • 逐个检查每个构造器是否满足条件
    • 如果已经找到更好的,就不需要再检查了
  3. 参数值匹配

    • 就像给每个职位找到最合适的候选人
    • 按照索引、类型、名称等优先级匹配参数值
  4. 类型转换

    • 就像把候选人的技能转换成职位要求的能力
    • 将配置的参数值(如字符串 "25")转换为构造器参数类型(如 Integer
  5. 计算匹配权重

    • 就像给每个候选人的匹配度打分
    • 精确匹配得分最高(权重最低),需要转换的得分较低(权重较高)
  6. 选择最佳构造器

    • 就像选择匹配度最高的候选人
    • 选择权重最低(匹配度最高)的构造器

8.3 关键发现

  1. 类型差异权重的量化

    • Spring 通过权重值量化类型差异
    • 精确匹配权重为 0,需要转换的权重为 8
    • 权重越低,匹配度越高
  2. ArgumentsHolder 的缓存机制

    • ArgumentsHolder 可以缓存类型转换的结果
    • 避免重复转换,提高性能
  3. 灵活的匹配策略

    • 支持多种参数匹配方式(索引、类型、名称、顺序)
    • 提供了很大的灵活性

8.4 本文的边界

本文主要聚焦于类型转换和匹配权重计算的机制,即如何将 Bean 定义中的参数值转换为构造器参数类型,以及如何通过类型差异权重选择最佳匹配的构造器。但是:

  • DependencyDescriptor 的处理:注解驱动的依赖注入如何处理,将在后续文章展开
  • 更复杂的类型转换场景:泛型、集合类型等的转换,将在后续文章展开

9. 参考资料

9.1 相关源码类和方法

本文涉及的主要源码类和方法:

  • ConstructorResolver.createArgumentArray() - 创建构造器参数数组的核心方法(本文重点)
  • TypeConverter.convertIfNecessary() - 类型转换的核心方法(本文重点)
  • ArgumentsHolder - 构造器参数值容器(本文重点)
  • TypeConverter - 类型转换器接口
  • BeanWrapper - Bean 包装器,提供类型转换能力

9.2 相关文章


上一篇createBean如何寻找构造器(三)------多参数构造器的匹配
下一篇:[createBean如何寻找构造器(五)------DependencyDescriptor与注解驱动(待发布)]


10. 总结

本文深入探索了 Spring 在构造器选择过程中的类型转换和匹配权重计算机制,重点分析了 createArgumentArray 方法的完整实现。通过本次探索,我们理解了:

  1. 构造器选择的完整流程:从候选构造器排序到最终选择最佳匹配的构造器
  2. 参数值匹配的多种策略:索引、类型+名称、类型、顺序等多种匹配方式
  3. 类型转换的详细过程 :如何使用 TypeConverter 进行类型转换
  4. 类型差异权重的计算规则:如何量化类型差异,选择最佳匹配
  5. ArgumentsHolder 的作用:如何保存转换结果,提高性能

虽然本文内容已经非常丰富,但构造器选择的完整机制还包括:

  • DependencyDescriptor 的处理逻辑
  • 更复杂的类型转换场景(泛型、集合类型等)
  • 构造器缓存机制的详细分析

这些内容将在后续文章中继续探索。让我们保持耐心,继续深入理解 Spring 的构造器选择机制!

相关推荐
-孤存-2 小时前
SpringBoot核心注解与配置详解
java·spring boot·后端
Hx_Ma162 小时前
BCrypt
java
We....2 小时前
鸿蒙与Java跨平台Socket通信实战
java·服务器·tcp/ip·arkts·鸿蒙
笃行客从不躺平2 小时前
Token 复习
java·分布式·spring cloud
Albert Edison2 小时前
【Python】函数
java·linux·python·pip
2301_818732062 小时前
项目启动报错,错误指向xml 已解决
xml·java·数据库·后端·springboot
码农阿豪3 小时前
Oracle 到金仓数据库迁移实战:一次真正“落地”的国产替代之旅
java·数据库·oracle
小王不爱笑1323 小时前
SpringBoot 整合 Ollama + 本地 DeepSeek 模型
java·spring boot·后端
毕设源码-钟学长3 小时前
【开题答辩全过程】以 高校宿舍分配系统设计与实现为例,包含答辩的问题和答案
java