首页 > 其他分享 >当未指定且存在多个构造器,实例化对象时Spring如何选择?

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

时间:2024-03-08 09:11:25浏览次数:28  
标签:构造方法 mbd Spring 未指定 实例 参数 new null 参数值

前言

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

标签:构造方法,mbd,Spring,未指定,实例,参数,new,null,参数值
From: https://www.cnblogs.com/guoxiaoyu/p/18055070

相关文章

  • spring - mvc - @Scheduled
    @Scheduled1.启用调度支持为了在Spring中启用对调度任务和@Scheduled注释的支持,我们可以使用Java启用样式注释:@Configuration@EnableSchedulingpublicclassSpringConfig{...}相反,我们可以在XML中做同样的事情:<task:annotation-driven>2.按固定延迟安排任务......
  • spring - springmvc - @EnableCaching
    @EnableCaching@EnableCaching注释在应用程序中启用注释驱动的缓存管理功能,并允许我们在应用程序中使用@Cacheable和@CacheEvict注释。具有类似功能的XML等效项是cache:*命名空间:@Configuration@EnableCachingpublicclassCacheConfig{@BeanpublicCacheMana......
  • SpringBoot 支付宝付款接口类、支付异步回调函数模板
    1.付款接口类1.1.引入Maven依赖<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.38.221.ALL</version></dependency>1.2.将下面代码保存为AlipayTemplate.java@Config......
  • SpringBoot-重要注解(1)
    ConfigurationProperties注解https://blog.csdn.net/skh2015java/article/details/120141409@Import、@ImportAutoConfigurationhttps://www.cnblogs.com/imyjy/p/16092825.html当我们需要在一个配置类中导入另一个Configuration配置类时,可以使用@Import、@ImportAuto......
  • Spring框架Bean对象的五个作用域
    ​ 一、前言:Bean对象简介在Spring项目中,那些由Spring IoC容器所管理的对象,称为bean。简单地讲,bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。 而bean定义以及bean相互间的依赖关系将通过配置元数据来描述。上一段描述......
  • spring - mvc
    springmvc1.@Autowired@ComponentpublicclassFooService{@AutowiredprivateFooFormatterfooFormatter;}2.通过@Qualifier自动装配例如,让我们看看如何使用@Qualifier注释来指示所需的bean。首先,我们将定义2个Formatter类型的bean:@Component("fooFo......
  • SpringBoot3+Consul配置,启动后,居然不读bootstrap.yml的配置文件,直接连本地localhost:8
    问题描述如题。bootstrap.yml的配置文件: consul控制台打印的日志: 解决方案:booststrap.yml的配置文件缩进搞错了,所以压根就没有读到配置。正确的缩进:  ......
  • spring-webClient-响应式http客户端
    1.WebClient简介WebClient是SpringWebFlux模块提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。WebFlux对标SpringMvc,WebClient相当于RestTemplate,同时也是Spring官方的Http请求工具。2.传统阻塞IO模型VS响应式IO模型传统阻塞IO模型RestTem......
  • spring-restTemplate-网络请求
    1,引言  现如今的IT项目,由服务端向外发起网络请求的场景,基本上处处可见!传统情况下,在服务端代码里访问http服务时,一般会使用JDK的HttpURLConnection或者Apache的HttpClient,不过这种方法使用起来太过繁琐,而且api使用起来非常的复杂,还得操心资源回收。  RestTempl......
  • Spring-@Bean-注解
    1.作用用于将对象存入spring的ioc容器中。@controller、@Service、@Component、@Configuration、@Repository等几个注解是一样的,都是负责将对象存入容器当中,而@Bean是用在方法上,将当前方法的返回值对象放到容器当中。2.使用@Bean一般出现在方法上面,也可用于自定义......