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

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

前言

在前面的讲解中,我们了解了如何获取构造器。当只有一个符合条件的构造器时,自然会选择它作为初始化的构造器。然而,在上一节中,我们遇到了一种特殊情况:当有多个符合条件的构造器时,返回的是一个数组。在这种情况下,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框架。

责任编辑:武晓燕 来源: 灵墨AI探索室
相关推荐

2011-03-17 16:18:14

2019-10-22 08:45:07

Java构造器编程语言

2023-03-15 08:42:06

form表单设计接口

2011-05-25 10:46:39

Javascript

2022-03-17 05:42:05

__init__Python

2015-07-30 13:28:56

网络虚拟化服务器虚拟化

2015-07-03 10:53:29

虚拟化

2009-07-21 12:35:00

Scala从构造器

2009-11-30 09:40:44

2019-06-18 16:00:18

打印机Windows 10未指定的设备

2021-03-08 08:40:25

Spring Bean 创建单例对象

2010-04-29 14:33:01

Unix系统

2021-08-19 09:59:07

Spring代码Java

2020-05-12 10:17:11

CIOIT采购决策

2011-06-07 15:27:02

SEO

2009-06-18 15:56:49

Struts和Spri

2009-03-24 10:30:35

SpringStruts集成

2021-07-28 10:35:14

云计算云计算环境云区域

2011-07-22 17:52:46

iPhone 时间 控件

2010-10-25 09:53:44

中小企业虚拟化服务器
点赞
收藏

51CTO技术栈公众号