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

2024年 3月 8日 58.4k 0

前言

在前面的讲解中,我们了解了如何获取构造器。当只有一个符合条件的构造器时,自然会选择它作为初始化的构造器。然而,在上一节中,我们遇到了一种特殊情况:当有多个符合条件的构造器时,返回的是一个数组。在这种情况下,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[] 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方法的整体流程,这样我们可以更清楚地理解其运作方式。

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

  • 当没有确定要使用的构造方法时,可以遍历类中的所有构造方法。
  • 当类中只存在一个无参构造方法时,可以直接使用该无参构造方法进行实例化,无需额外的选择操作。
  • 在选择构造方法时,需要确定所需参数个数的最小值。若已传入构造方法参数值,则所选构造方法的参数个数必不少于传入值的个数;若未传入参数值,则需检查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);
     }
    }
  • 对候选构造方法进行排序。首先,将public修饰的构造方法排在最前面;若所有构造方法均为public,那么参数个数越多的构造方法越靠前。
  • 遍历每个构造方法
  • 在调用getBean()方法时,如果不指定构造方法的参数值,系统会根据构造器中的参数类型和参数名来匹配相应的bean对象。
  • 在调用getBean()方法时,如果指定了构造方法的参数值,系统会直接利用这些参数值来实例化bean对象
  • 在确定构造方法时,尽管找到了匹配的构造方法参数值,但并不意味着这个构造方法是最佳选择。因此,需要考虑是否存在多个构造方法匹配了相同的值。在这种情况下,系统将会根据值和构造方法类型之间的匹配程度进行评分,以找到最佳匹配的构造方法。
  • 分值越低越匹配

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

    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框架。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论