【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 的构造器选择机制!

相关推荐
沉鱼.4427 分钟前
第十二届题目
java·前端·算法
努力的小郑1 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
赫瑞1 小时前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor3562 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3562 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁2 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp2 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥2 小时前
多进程和多线程的特点和区别
java·开发语言·jvm
惜茶3 小时前
vue+SpringBoot(前后端交互)
java·vue.js·spring boot
宁瑶琴3 小时前
COBOL语言的云计算
开发语言·后端·golang