22. Spring源码篇之推断构造方法

简介

很多时候我们的构造器都不止一个,那么spring怎么选择的呢,签名介绍了推断构造方法的扩展点,可以使用@Autowired注解去选择使用哪个构造器,但是即使这样也有可能有多个Autowired且required为false的构造器,那么还是得选择

前面我们介绍过了@Bean的实例化,其实推断构造器的逻辑与其相差不多。

源码分析

源码在 org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor

// chosenCtors 指定使用哪几个构造器,explicitArgs 参数
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;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) { // 一般都进入Constructor<?>[] candidates = chosenCtors;if (candidates == null) {// 进入这表示没有确定要使用哪个构造器,那么拿到该类的所有构造器放到candidates候选Class<?> beanClass = mbd.getBeanClass();candidates = (mbd.isNonPublicAccessAllowed() ?beanClass.getDeclaredConstructors() : beanClass.getConstructors());}if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {// 构造器只有一个,没有指定构造器参数,也没有预先设置constructorArgumentValues,那么直接实例化instantiateConstructor<?> 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;}}boolean autowiring = (chosenCtors != null ||mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);ConstructorArgumentValues resolvedValues = null;// 要选择构造器参数最多的,如果小于这个值那么pass,如果大于更新int minNrOfArgs;if (explicitArgs != null) {// 如果指定传入了参数值,那么minNrOfArgs不能低于传入的长度minNrOfArgs = explicitArgs.length;}else {// 这种方式指定构造器的值有点特殊,前面文章也介绍过 // 它可以指定参数下标的值,比如指定了0,2那么表示指定了第一个参数和第三个参数的值,虽然指定参数只有 2个,但是minNrOfArgs也至少得是 3ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();resolvedValues = new ConstructorArgumentValues();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) {// 如果说已经选出来要用的构造器和入参对象,但是指定的入参比当前构造器参数还多,那么直接break,因为排了序,后面参数肯定更少break;}// 如果参数个数小于要求的参数个数,passif (parameterCount < minNrOfArgs) {continue;}ArgumentsHolder argsHolder;Class<?>[] paramTypes = candidate.getParameterTypes();if (resolvedValues != null) {// resolvedValues有值那么explicitArgs肯定就没值,所以进入这里是因为BeanDefinition指定了参数try {// 如果在构造方法上使用了@ConstructorProperties,那么就直接取定义的value作为构造方法的参数名String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);// 找出参数名称,反射 & 本地变量表if (paramNames == null) {ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();if (pnd != null) {paramNames = pnd.getParameterNames(candidate);}}// 根据BeanDefinition中定义的参数,以及通过name从beanFactory获取到Bean// 最终组成为argsHolder ,这里面的详细过程后面文章讲argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);}catch (UnsatisfiedDependencyException ex) {if (causes == null) {causes = new ArrayDeque<>(1);}// 记录异常,后面推断不出方法便抛出异常causes.add(ex);continue;}}else {// resolvedValues为null,那么explicitArgs就一定有值// 通过getBean传入的,那么参数个数必须一致if (parameterCount != explicitArgs.length) {continue;}argsHolder = new ArgumentsHolder(explicitArgs);}// 根据参数类型和找到的参数对象计算出来一个匹配值,值越小越匹配int typeDiffWeight = (mbd.isLenientConstructorResolution() ?argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));// Choose this constructor if it represents the closest match.// 值越小越匹配if (typeDiffWeight < minTypeDiffWeight) {// 如果根据当前的方法参数计算出来的评分更小些,那么应该使用该构造方法来创建BeanconstructorToUse = candidate;argsHolderToUse = argsHolder;argsToUse = argsHolder.arguments;minTypeDiffWeight = typeDiffWeight;ambiguousConstructors = null; // 同时也就不存在模棱两可的方法了}// 如果评分一样的,那么表示推断不出使用哪个方法构造Bean,最终找不出来要抛异常else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {if (ambiguousConstructors == null) {ambiguousConstructors = new LinkedHashSet<>();ambiguousConstructors.add(constructorToUse);}ambiguousConstructors.add(candidate);}}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()) {// 表示推断不出使用哪个方法构造Bean,抛异常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);}if (explicitArgs == null && argsHolderToUse != null) {// 找到了,缓存起来argsHolderToUse.storeCache(mbd, constructorToUse);}}// 通过反射调用uniqueCandidate返回一个对象,然后设置到BeanWrapper返回bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));return bw;
}

总结

以上便是推断构造方法的关键逻辑,因为构造方法可能有多个,这个时候如果指定了参数,那么直接根据指定的参数匹配方法,如果没有指定参数,那么spring会根据评分算法帮我们找出方法

至于其中的方法评分的算法,不是重点,大概就是说匹配成都越高的分越低,分越低就优先级越高,如果匹配度不高那么是要加分的,比如当前构造参数类型是值的类型的父类,加两分,当前类型是个接口加一分

关于这个算法,后面出文章讲解


欢迎关注,学习不迷路!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/162402.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QLineEdit响应editingFinished实现只响应一次

QLineEdit中editingFinished槽函数会在按下enter时响应两次&#xff0c;分别是按下enter和失去焦点的时候&#xff0c;为了能让 QLineEdit只响应其中一次&#xff0c;需定义一个全局变量m_bFlagEnter保存是否enter是否按下&#xff0c;按下后&#xff0c;则在失去焦点的时候做对…

Unity发布IOS后,使用xcode打包报错:MapFileParser.sh:Permissiondenied

1.错误提示 使用xcode打包错误提示&#xff1a;/Users/mymac/Desktop/myproject/MapFileParser.sh: Permission denied 2.解决方案 打开控制台输入&#xff1a;chmod ax /Users/mymac/Desktop/myproject/MapFileParser.sh。按回车键执行&#xff0c;然后重新使用xcode发布程序…

【开源】基于JAVA的在线课程教学系统

项目编号&#xff1a; S 014 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S014&#xff0c;文末获取源码。} 项目编号&#xff1a;S014&#xff0c;文末获取源码。 目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2…

机器学习之数据清洗和预处理

目录 Box_Cox Box_Cox Box-Cox变换是一种用于数据预处理和清洗的方法&#xff0c;旨在使数据更符合统计模型的假设&#xff0c;特别是对于线性回归模型。这种变换通过调整数据的尺度和形状&#xff0c;使其更加正态分布。 Box-Cox变换的定义是: y ( λ ) { y λ − 1 λ , i…

【深度学习】卷积神经网络(CNN)

一、引子————边界检测 我们来看一个最简单的例子&#xff1a;“边界检测&#xff08;edge detection&#xff09;”&#xff0c;假设我们有这样的一张图片&#xff0c;大小88&#xff1a; 图片中的数字代表该位置的像素值&#xff0c;我们知道&#xff0c;像素值越大&#…

QQ怎么备份聊天记录?3个方法教你快速备份!

QQ聊天记录作为用户和亲人、好友以及同事之间沟通的凭证&#xff0c;可以帮助我们回忆起过去的交流内容。如果我们不小心误删了QQ聊天记录或者更换了新手机&#xff0c;那么这时候就需要备份聊天记录。qq怎么备份聊天记录呢&#xff1f;本文将介绍3个简单方法&#xff0c;帮助您…

MySQL - 4种基本索引、聚簇索引和非聚索引、索引失效情况

目录 一、索引 1.1、简单介绍 1.2、索引的分类 1.2.1、主键索引 1.2.2、单值索引&#xff08;单列索引、普通索引&#xff09; 1.2.3、唯一索引 1.2.4、复合索引 1.2.5、复合索引经典问题 1.3、索引原理 1.3.1、主键自动排序 1.3.2、索引的底层原理 1.3.3、B 树和 B…

创作4周年

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言机…

哈希表-set、map

当需要判断一个元素是否在集合中时&#xff0c;就使用哈希法 散列表&#xff08;Hash table&#xff0c;也叫哈希表&#xff09;&#xff0c;是根据键&#xff08;Key&#xff09;而直接访问在内存存储位置的数据结构。 哈希表中关键码就是数组的索引下标&#xff0c;然后通过…

Web框架Flask

Web框架Flask Flask简介第一个Flask应用Flask路由Flask路由变量规则Flask URL 构建Flask重定向Flask静态文件Flask渲染模板Flask请求对象Flask响应对象Flask CookiesFlask错误Flask JSON 格式的 APIFlask SessionFlask 消息闪现Flask日志Flask蓝图Flask视图Flask Jinja2 模板F…

微信消息提醒

有时候同事没有打开微信&#xff0c;重要的信息可以设置提醒

app小程序开发的重点在哪里?|企业软件定制网站建设

app小程序开发的重点在哪里&#xff1f;|企业软件定制网站建设 App小程序定制开发是近年来快速发展的一项技术服务&#xff0c;随着移动互联网的普及和用户需求的不断升级&#xff0c;越来越多的企业和个人开始关注和需求定制化的小程序开发。那么&#xff0c;对于app小程序定制…

Springboot_文件下载功能(前端后端)

遇到的问题&#xff1a; 文件下载后文件一直被破坏&#xff0c;无法正常打开文件名乱码&#xff0c;如图 刚开始一直在纠结&#xff0c;是不是后端没有写对&#xff0c;然后导致下载不能使用 后来搜索了一些资料&#xff0c;发现后端没什么问题 然后就开始找到其他项目对比…

头发的方向图(2D和3D)与合成

首先&#xff0c;我们从一个不受光照限制的环境中拍摄一组输入图像&#xff0c;这些图像包含了头发的不同视角和姿态。我们对这些图像进行半自动的分割&#xff0c;将头发从背景中分离出来&#xff0c;然后使用PMVS &#xff0c;一种先进的多视角立体算法&#xff0c;来重建一个…

Qt 问题 判断QTreeWidget的子节点的父节点是否可见

bool JudgeParentItemVisible(QTreeWidgetItem * pLayerItem) {bool bVisible true;QTreeWidgetItem * pParentItem (QTreeWidgetItem *)pLayerItem->parent(); //获取父节点if (pParentItem ! NULL) //父节点不为空{if (pParentItem->checkState(0) Qt::CheckState::…

广播组播、本地套接字通信、wireshark、以太网帧格式、三次握手四次挥手

广播&#xff08;使用 UDP 套接字&#xff09; 广播地址&#xff1a;主机号最大的地址。 广播&#xff1a;给所在局域网的所有主机发送数据报。&#xff08;之前的数据报发送方式是单播。&#xff09; 以下情况中使用广播&#xff1a; 局域网 搜索协议。 比如家中的智能产品&a…

局域网共享打印机共享,简单至简至一键处理011bDll等问题

一、电脑系统是否激活&#xff08;可选&#xff09; 二、确保主客户端PC在同一局域网内&#xff08;可选&#xff09; 可以通过ping 目标地址 如ping 192.168.1.202&#xff1b;看是否可以正常通信 下面是惠普类型打印机共享问题关键&#xff08;文本记得保存&#xff09; …

Redisson 分布式锁的最佳实践

Redisson 分布式锁的最佳实践 第一、添加依赖第二、添加redisson配置类第三、添加测试类测试结果扩展知识redisson锁中lock方法和tryLock方法有什么区别锁续约 注意事项 引言 在现代分布式系统中&#xff0c;处理并发问题是至关重要的。分布式锁是解决这类问题的关键工具之一。…

双11再创新高!家电行业如何通过矩阵管理,赋能品牌增长?

双11大促已落下帷幕&#xff0c;虽然今年不再战报满天飞&#xff0c;但从公布的数据来看&#xff0c;家电行业整体表现不俗。 根据抖音电商品牌业务发布的收官战报&#xff0c;家电行业创造了成交新纪录&#xff0c;整体同比增长125%。快手官方数据显示&#xff0c;消电家居行业…

深入理解JMM以及并发三大特性(1)

文章目录 1. 并发与并行2. JMM3. 并发三大特性4.总结 1. 并发与并行 并行&#xff1a;指在同一时刻&#xff0c;有多条指令在多个处理器上同时执行。所以无论从微观还是宏观来看&#xff0c;二者都是一起执行的。 并发&#xff1a;指在同一时刻只能有一个指令执行&#xff0c;…