• 当未指定且存在多个构造器,实例化对象时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> ambiguousConstructors = null;
    		Deque 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框架。

  • 相关阅读:
    Mac 远程 Ubuntu
    `算法知识` 模意义下的乘法逆元
    JVM虚拟机:垃圾回收器ZGC和Shenandoah算法
    C++_抽象类
    怎样自学黑客技术_黑客/网络安全入门资料
    [软件工具]ARW文件批量转图片jpg工具使用教程
    笔记 | 嵌入式系统概论
    forms组件补充与ModelForm简单使用与cookie与session
    vim 从嫌弃到依赖(22)——自动补全
    Linux 最大可以打开多少文件描述符?
  • 原文地址:https://www.cnblogs.com/guoxiaoyu/p/18055070