当未指定且存在多个构造器,实例化对象时Spring如何选择?
前言
在前面的讲解中,我们了解了如何获取构造器。当只有一个符合条件的构造器时,自然会选择它作为初始化的构造器。然而,在上一节中,我们遇到了一种特殊情况:当有多个符合条件的构造器时,返回的是一个数组。在这种情况下,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方法的整体流程,这样我们可以更清楚地理解其运作方式。
如果没有明确确定要使用的构造方法,或者已确定构造方法但其所需传入参数值尚未确定。
比如这样配置:
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 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框架。