请你谈谈:BeanDefinition类作为Spring Bean的建模对象,与BeanFactoryPostProcessor之间的羁绊

那么,我们如何理解Spring Bean的建模对象呢?简而言之,它是指用于描述和配置Bean实例化过程的模型对象。有人可能会提出疑问,既然只需要Class(类)就可以实例化一个对象,Class作为类的元数据,可以视作对象的建模对象,为何Spring还需要其他机制来建立Bean呢?这其中的关键在于,Class本身虽然包含了类的定义和实例化的基础信息,但它无法涵盖Spring框架中Bean的所有特性和配置。例如,Bean的作用域(Scope)、注入模型(Injection Model)、是否采用懒加载(Lazy Initialization)等属性,这些都是在Spring容器级别进行配置和管理的,而这些信息无法通过简单的Class元数据来表达。

因此,为了更全面地描述和管理Bean,Spring引入了一个名为BeanDefinition的类。BeanDefinition类作为Spring Bean的建模对象,能够抽象出Bean的各种属性和配置信息,包括但不限于作用域、生命周期回调、初始化方法、销毁方法、依赖注入等。通过BeanDefinition,Spring能够精确地控制Bean的实例化过程,确保Bean能够按照预期的方式被创建、配置和管理。
在这里插入图片描述
针对上述图示的详细文字说明如下:在一个给定的场景中,我们假设磁盘上存有N个.java源文件。首先,我们需要通过Java编译器将这些源文件逐一编译成相应的.class字节码文件。接着,当Java虚拟机(JVM)启动时,它会按照特定的类加载机制,将这些.class文件从磁盘加载到JVM的内部内存中。一旦.class文件被成功加载到内存中,JVM便能根据这些文件中定义的类模板信息来执行后续的操作。当JVM的字节码执行器在执行过程中遇到new关键字时,它会根据该关键字所指向的类的模板信息,在JVM的堆内存中为该类实例化一个对象,并为其分配相应的内存空间。这一过程确保了对象在JVM中的正确创建和初始化。

但是spring的bean实例化过程和一个普通java对象的实例化过程还是有区别的。
在这里插入图片描述
前提:假设在你的项目或者磁盘上有X和Y两个类,X是被加了spring注解的,Y没有加spring的注解;也就是正常情况下当spring容器启动之后通过getBean(X)能正常返回X的bean,但是如果getBean(Y)则会出异常,因为Y不能被spring容器扫描到不能被正常实例化;

在Spring容器启动的过程中,ConfigurationClassPostProcessor这一Bean工厂的后置处理器会被调用,以完成特定配置的扫描和解析。这里的“扫描”实际上是指Spring从类路径中识别并读取类的元数据。然而,读取到的类的元数据,如类的类型、名称和构造方法等,并不会直接存储在Class对象中。尽管Class对象确实包含了这些基本信息,但Spring在实例化Bean时,还需要考虑更多的配置属性,如作用域(scope)、延迟加载(lazy)、依赖关系(dependsOn)等。

为了满足这些需求,Spring设计了一个名为BeanDefinition的类,用于存储和管理Bean的完整配置信息。因此,当Spring读取到类的元数据后,

①它会为每个类实例化一个BeanDefinition对象,通过该对象的各种set方法将相关信息(包括从Class对象获取的元数据以及额外的配置属性)存储起来。

②在扫描过程中,每当Spring遇到一个符合规则的类,它都会实例化一个新的BeanDefinition对象,并根据类的名称自动生成一个Bean的名称(例如,对于名为IndexService的类,Spring会按照其命名规则生成一个名为indexService的Bean名称)。当然,Spring也提供了灵活的命名策略,允许开发者自定义名称生成器以覆盖默认行为。

③随后,Spring会将这个BeanDefinition对象及其对应的Bean名称存储在一个Map中,其中键(key)为Bean名称,值(value)为BeanDefinition对象。这个过程确保了Bean的元数据和配置信息在Spring容器中的有序管理和高效检索。至此,上述流程中的第①、②、③步便完成了,为后续的Bean创建和依赖注入等操作奠定了基础。

这里需要说明的是spring启动的时候会做很多工作,不仅仅是完成扫描,在扫描之前spring还干了其他大量事情;比如实例化beanFacctory、比如实例化类扫描器等等。

Appconfig.java
@ComponentScan("com.luban.beanDefinition")
@Configuration
public class Appconfig {
}X.java
@Component
public class X {public X(){System.out.println("X Constructor");}
}Y.java
public class Y {
}Test.java
public class Test{public static void main(String[] args) {AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();ac.register(Appconfig.class);ac.refresh();}}

在上述代码示例中,存在两个类:X和Y。其中,X类被标注了某个注解,而Y类则未被标注。在X类中,存在一个构造方法,该方法在X类实例化时会输出"X Constructor"。根据Spring框架的工作原理,当Spring容器启动时,它会执行一系列的初始化步骤,其中包括对带有特定注解的类的扫描和解析。

具体来说,在Spring容器的初始化过程中,会调用org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法。这个方法负责执行Bean工厂的后置处理器,其中就包括了对带有特定注解的类的扫描和解析。为了验证这一过程,我在该方法上设置了一个断点。

在Spring框架中,invokeBeanFactoryPostProcessors 是一个非常重要的方法,它负责在Spring容器初始化过程中调用所有实现了 BeanFactoryPostProcessor 接口的bean。这些后处理器允许在bean的定义被加载到Spring的bean工厂(BeanFactory)之后,但在bean被实例化之前,对bean定义进行修改或添加。

当应用程序启动并触发Spring容器初始化时,会调用invokeBeanFactoryPostProcessors方法。在方法执行之前,检查Spring内部的beanDefinitionMap(或类似的存储结构),发现其中并没有键为"x"的元素,这说明此时X类尚未被扫描和解析。

随着invokeBeanFactoryPostProcessors方法的执行,Spring开始扫描并解析带有注解的类。当扫描到X类时,它会将X类的信息解析为一个BeanDefinition对象,并将该对象以键为"x"的形式存储到beanDefinitionMap中。

完成扫描和解析后,我再次检查了beanDefinitionMap,发现其中已经包含了键为"x"的元素,其对应的值正是一个BeanDefinition对象。然而,此时并未发现控制台输出"X Constructor",这说明尽管X类已经被扫描并解析为一个BeanDefinition对象,但X类的实例并未被创建。

这个例子清晰地展示了Spring框架在初始化过程中的一个关键步骤:首先扫描并解析带有注解的类,将其信息存储为BeanDefinition对象并放入beanDefinitionMap中;随后,在适当的时候(如依赖注入时),才会根据这些BeanDefinition对象来实例化相应的类。关于beanDefinitionMap的详细工作原理和用途,将在后续的文章中进一步探讨。在本文中,读者只需理解它是一个专门用于存储BeanDefinition对象的集合即可。

在这里插入图片描述
在这里插入图片描述

④在Spring将类所对应的BeanDefinition对象存储到Map之后,它会进一步调用程序员提供的Bean工厂后置处理器BeanFactoryPostProcessor。那么,什么是BeanFactoryPostProcessor呢?在Spring的架构中,BeanFactoryPostProcessor是一个接口,用于定义对BeanFactory进行额外处理或修改的逻辑。任何实现了该接口的类都可以被视为一个BeanFactoryPostProcessor。关于BeanFactoryPostProcessor接口的详细源码解析,我们将在后续的文章中深入探讨。在此,我们先简要概述其基本作用。

值得注意的是,Spring内部也实现了BeanFactoryPostProcessor接口,例如ConfigurationClassPostProcessor。该类在Spring源码中扮演着举足轻重的角色,它负责执行诸如扫描配置类、解析配置信息等重要功能。在我看来,ConfigurationClassPostProcessor是理解Spring源码过程中不可或缺的关键类之一。由于其功能复杂且广泛,我们将在后续的分析中逐一深入探讨。现在,让我们先来看一下这个类的类结构图,以便对其有一个初步的了解。
在这里插入图片描述
ConfigurationClassPostProcessor作为Spring框架中的一个核心组件,实现了多个接口,其中与本文直接相关的是BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。尽管BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,但为了更好地理解其角色和用途,我们将其视为两个独立的接口。

在Spring的启动过程中,ConfigurationClassPostProcessor发挥了关键作用。具体来说,当Spring执行①②③步骤时,它实际上是调用了ConfigurationClassPostProcessor中实现的BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法。这一步骤负责处理与BeanDefinition注册相关的逻辑。

进入第④步时,Spring会执行BeanFactoryPostProcessor接口的postProcessBeanFactory方法。这里需要明确的是,Spring首先会调用ConfigurationClassPostProcessor自身的postProcessBeanFactory方法,这是因为它本身也实现了该接口。之后,如果应用程序开发者提供了自定义的BeanFactoryPostProcessor实现,Spring也会依次调用这些自定义的postProcessBeanFactory方法。

为了更清晰地表达这一过程,我们注意到第④步在图中是以红色虚线表示的,这是因为这一步的执行依赖于开发者是否提供了自定义的BeanFactoryPostProcessor。然而,不论开发者是否提供了自定义实现,Spring都会执行其内置的BeanFactoryPostProcessor,即ConfigurationClassPostProcessor。因此,图表在描述第④步时确实可以进一步细化,以明确表示Spring对内置和自定义BeanFactoryPostProcessor的执行。

综上所述,Spring在启动过程中通过ConfigurationClassPostProcessor的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口实现了对BeanDefinition的注册和Bean工厂的后处理。无论是内置的还是自定义的BeanFactoryPostProcessor,都在Spring的启动流程中扮演着重要角色。

重点:我们用自己的话总结一下BeanFactoryPostProcessor的执行时机(不管内置的还是程序员提供):

BeanFactoryPostProcessor的执行时机是在Spring容器创建并初始化Bean定义之后,但在实例化任何Bean之前。这是一个关键的扩展点,允许开发者在Spring容器完成基本的Bean定义加载和解析之后,对BeanFactory的内容进行额外的处理或修改。

无论是Spring内置的BeanFactoryPostProcessor实现(如ConfigurationClassPostProcessor),还是程序员自己提供的自定义BeanFactoryPostProcessor实现,它们的postProcessBeanFactory方法都会在容器准备实例化Bean之前被调用。

具体来说,当Spring容器启动时,它会首先读取配置文件或注解信息,并将这些配置转化为内部的Bean定义(BeanDefinition)。完成这一步之后,Spring会查找所有实现了BeanFactoryPostProcessor接口的类,并依次调用它们的postProcessBeanFactory方法。这些处理器可以对BeanFactory中的Bean定义进行修改、添加或删除,从而影响最终的Bean实例化过程。

因此,BeanFactoryPostProcessor为开发者提供了一个在Spring容器启动过程中干预Bean定义的机会,使得开发者能够根据自己的需求对容器进行定制和扩展。

那么第④步当中提到的执行程序员提供的BeanFactoryPostProcessor到底有什么意义呢?程序员提供BeanFactoryPostProcessor的场景在哪里?有哪些主流框架这么干过呢?

首先回答第一个问题,意义在哪里?可以看一下这个接口的方法签名:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
throws BeansException;

其实讨论这个方法的意义就是讨论BeanFactoryPostProcessor的作用或者说意义,参考这个方法的一句:

Modify the application context’s internal bean factory after its standard initialization
在应用程序上下文的标准初始化之后修改内部的BeanFactory

在Spring框架中,BeanFactoryPostProcessor(此处特指直接实现此接口的后置处理器,不涉及BeanDefinitionRegistryPostProcessor)被视为一个关键的扩展点。Spring框架通过提供这样的扩展点,旨在赋予开发者对BeanFactory初始化过程进行干预的能力,这是深入学习Spring源码并对其进行二次开发或构建优雅插件的重要一环。需要特别强调的是,这里的核心焦点在于“初始化过程”,而非“实例化过程”。这两者在Spring框架的上下文中具有显著的区别,特别是在阅读Spring源码时,应特别留意这两个术语的使用。

深入Spring的源码,我们会发现整个容器初始化过程实质上是由各种后置处理器(PostProcessor)的调用序列所构成的。这些后置处理器大致可以分为两类:一类关注于bean的实例化过程,而另一类则关注于bean的初始化过程。这种分类并非主观臆想,而是基于Spring框架对初始化和实例化概念的明确区分。熟悉Spring后置处理器体系的开发者,从其命名规则中就能洞察到Spring对这两者之间的严格区分。

严谨地阐述,BeanFactoryPostProcessor的主要作用并不在于干预BeanFactory的实例化过程,即BeanFactory如何被创建(new)出来。然而,一旦BeanFactory实例化完成,BeanFactoryPostProcessor便能够介入其后的属性配置和修改阶段,也就是我们通常所说的“初始化”过程。

具体来说,BeanFactoryPostProcessor接口中定义的方法postProcessBeanFactory接受一个类型为ConfigurableListableBeanFactory的参数,这个参数代表了已经实例化并配置好的BeanFactory对象。由于Spring在调用postProcessBeanFactory方法时传递的是已经准备好的beanFactory实例,因此开发者可以在这个方法中对BeanFactory进行进一步的配置或修改。

这意味着,通过实现BeanFactoryPostProcessor接口并覆盖postProcessBeanFactory方法,开发者可以定制BeanFactory的初始化过程,例如添加、修改或删除bean定义,调整bean的属性等。然而,需要强调的是,在操作过程中应当遵循Spring框架的设计原则和最佳实践,以确保系统的稳定性和可维护性。

在处理BeanFactory对象时,仅仅进行简单的打印(如使用System.out.println)是远远不够的,这种做法并不能充分发挥BeanFactory的功能,更不能称之为“肆意妄为”。实际上,BeanFactory提供了丰富的功能和API供开发者使用,但我们必须先深入理解其特性和API,才能有效地利用它。

尽管BeanFactory本身具有复杂性,不在此展开详细讨论,但与本文内容紧密相关的是beanDefintionMap(存储BeanDefinition的集合),这个重要的数据结构就定义在BeanFactory中。BeanFactory也提供了相应的API供程序员操作这个Map,比如允许我们修改其中已存在的BeanDefinition对象,或者向其中添加新的BeanDefinition对象。这些操作都需要开发者对BeanFactory和BeanDefinition有深入的了解,以确保在操作时遵循最佳实践,并保持系统的稳定性和可维护性。

@Component
public class TestBeanFactoryPostPorcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 转换为子类,因为父类没有添加beanDefintion对象的apiDefaultListableBeanFactory defaultbf =
(DefaultListableBeanFactory) beanFactory;// new一个Y的beanDefinition对象,方便测试动态添加GenericBeanDefinition y= new GenericBeanDefinition();y.setBeanClass(Y.class);// 添加一个beanDefinition对象,原本这个Y没有被spring扫描到defaultbf.registerBeanDefinition("y", y);// 得到一个已经被扫描出来的beanDefintion对象x// 因为X本来就被扫描出来了,所以是直接从map中获取BeanDefinition x = defaultbf.getBeanDefinition("x");// 修改这个X的beanDefintion对象的class为Z// 原本这个x代表的class为X.class;现在为Z.classx.setBeanClassName("com.luban.beanDefinition.Z");}}

在项目中,我们定义了三个类:X、Y和Z。其中,仅有类X被标注了@Component注解。因此,在代码执行到特定方法时,Spring容器仅扫描并识别到了类X,导致BeanFactory中的beanDefinitionMap仅包含与类X相对应的BeanDefinition对象。

为了演示如何动态添加自定义的BeanDefinition对象,笔者首先手动创建了一个与类Y相对应的BeanDefinition实例,并通过调用registerBeanDefinition(“y”, yBeanDefinition)方法,将其注册到beanDefinitionMap中。这一步骤展示了如何向Spring容器中动态添加一个自行实例化的BeanDefinition对象。

随后,为了演示如何动态修改已扫描的BeanDefinition对象,笔者通过调用getBeanDefinition(“x”)方法获取了与类X相对应的已存在BeanDefinition对象。然后,通过调用xBeanDefinition.setBeanClassName(“Z”)方法,将原本与类X关联的BeanDefinition对象所指向的类更改为Z。这一步骤展示了如何在运行时动态修改Spring容器中已扫描和注册的BeanDefinition对象。

public static void main(String[] args) {AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext();ac.register(Appconfig.class);ac.refresh();//正常打印System.out.println(ac.getBean(Y.class));//正常打印System.out.println(ac.getBean(Z.class));//异常打印//虽然X加了注解,但是被偷梁换柱了,故而异常System.out.println(ac.getBean(X.class));}

总结上述图示内容,Spring实例化一个Bean的过程并非直接与你所提供的类相关,而是与特定的BeanDefinition对象所映射的类直接相关。通常情况下,一个BeanDefinition对象对应一个类,但特殊情况下也可能存在不同映射关系。为了更形象地解释这一点,可以借用一个比喻:假设读者你喜欢一位女性(小A),而小A则对笔者有所好感。然而,你不能因此就推断你也喜欢笔者,因为两者之间的喜好关系并不具有传递性。此外,需要强调的是,笔者在此仅作为比喻中的一个角色,与现实中的性别倾向无关,这一点应明确区分。

综上所述,BeanFactoryPostProcessor是Spring框架中一个强大的扩展点,允许程序员在Bean生命周期的特定阶段对BeanFactory进行深入的定制和修改。通过合理地利用这一特性,程序员可以构建出更加灵活、可维护的Spring应用。

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

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

相关文章

springboot websocket 知识点汇总

以下是一个详细全面的 Spring Boot 使用 WebSocket 的知识点汇总 1. 配置 WebSocket 添加依赖 进入maven官网, 搜索spring-boot-starter-websocket,选择版本, 然后把依赖复制到pom.xml的dependencies标签中 配置 WebSocket 创建一个配置类 WebSocketConfig&…

mysql不初始化升级

1、下载mysql,下载地址:MySQL :: Download MySQL Community Server 2、解压下载好的mysql,修改配置文件的datadir指定目录为当前数据存储的目录 3、通过管理员cmd进入新版本mysql的bin目录, 然后执行命令安装mysql服务&#xff…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(七)-通过无人机实现无线接入的独立部署

引言 本文是3GPP TR 22.829 V17.1.0技术报告,专注于无人机(UAV)在3GPP系统中的增强支持。文章提出了多个无人机应用场景,分析了相应的能力要求,并建议了新的服务级别要求和关键性能指标(KPIs)。…

JVM:垃圾回收器

文章目录 一、介绍二、年轻代-Serial垃圾回收器三、老年代-SerialOld垃圾回收器四、年轻代-ParNew垃圾回收器五、老年代-CMS(Concurrent Mark Sweep)垃圾回收器六、年轻代-Parllel Scavenge垃圾回收器七、Parallel Old垃圾回收器八、G1垃圾回收器 一、介…

仅在少数市场发售?三星Galaxy Z Fold 6 Slim折叠屏手机更轻更薄

在智能手机的创新之路上,三星一直是行业的领跑者之一。随着Galaxy Z Fold系列的不断进化,三星再次突破技术边界,推出了更为轻薄的Galaxy Z Fold 6 Slim。 这款新型折叠屏手机以其独特的设计和卓越的性能,为用户带来了全新的使用体…

护眼台灯真的护眼吗?要注意学生如何正确使用台灯!

孩子们面临着越来越多的视力挑战,在近视学生中,近10%为高度近视,且占比随年级升高而增长。幼儿园6岁儿童中有1.5%为高度近视,而高中阶段则达到了17.6%。青少年是国家的未来和希望,而他们的视力健康却面临着前所未有的挑…

一篇讲清楚怎么选算力租赁平台

选择算力租赁平台时,需要考虑多个因素以确保找到最适合自己需求的服务。以下是一些关键点,可以帮助您做出明智的选择: 明确需求:首先,确定您的项目需要哪种类型的计算资源,比如CPU、GPU或FPGA,以…

Cadence23学习笔记(二)

原理图设计界面中就可以直接新建PCB: 亲测:需要画完原理图,并且DRC通过之后才可以! 放置完元器件之后要规定元件的Footprint ,注意PCB封装名要和库文件中的名字对应: DRC按钮: 点击图标 N, 生成第一网表&…

车载音视频MediaPlayer优化方案

媒体播放现状 从手机到车载,在很多地方还是有很大的不同。针对多媒体的场景Android车机目前大部分结构大致结构如下图: 从以上图看出的问题: 各个音视频APP单独实现播控界面,播放链路不一致,使用的底层播放器和音频焦…

基于Spring Boot的高校后勤餐饮管理系统

1 项目介绍 1.1 研究背景 “互联网”时代的到来,既给高校后勤管理发展带来了机遇,也带来了更大的挑战。信息化应用已经开始普及,传统的高校后勤餐饮管理模式往往存在着效率低下、信息不透明、资源浪费等问题,已经难以满足现代高…

Linux系统之部署经典魔塔小游戏

Linux系统之部署经典魔塔小游戏 一、魔塔小游戏介绍1.1 魔塔小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看apa…

STM32第十八课:SPIFlash

目录 需求一、SPI概要二、SPI配置1.开时钟2.配置IO3.配置&使能SPI 三、FLash操作函数1.SPI发送数据2.FLASH写使能3.FLASH等待操作完成4.FLASH页写操作5.FLASH读操作6.FLASH扇区擦除 四、需求实现 需求 通过SPI控制FLash进行数据的保存和删除。 一、SPI概要 在我们使用UA…

【python】OpenCV—European Article Number

参考学习来自:OpenCV基础(25)条码和二维码扫的生成与识别 1 条形码介绍 EAN-13是欧洲物品编码(European Article Number)的缩写,是一种广泛使用的条形码标准,特别是在超级市场和其它零售业中。…

OpenCV解决验证码(数字和字母)识别(Python)

文章目录 前言一、准备验证码图片 前言 OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库。它支持Windows、Linux、Mac OS、Android和iOS等多个操作系统,提供了丰富的图像处理和计算机视觉功能,包括但…

RPC与服务的注册发现

文章目录 1. 什么是远程过程调用(RPC)?2. RPC的流程3. RPC实践4. RPC与REST的区别4.1 RPC与REST的相似之处4.2 RPC与REST的架构原则4.3 RPC与REST的主要区别 5. RPC与服务发现5.1 以zookeeper为服务注册中心5.2 以etcd为服务注册中心 6. 小结参考 1. 什么是远程过程调用(RPC)?…

(自用)网络编程

OSI七层协议模型 (open system interconnection) 应用层————为应用数据提供服务 表示层————数据格式转化,数据加密 会话层————建立、维护和管理会话 传输层————建立、维护和管理端到端的链接,控制数据传输的方式 网络层————数据…

手机删除的文件能恢复吗?删除不等于永别,3个技巧助你找回

安卓手机中的文件,就像是数字世界里的繁星,记录着我们的点点滴滴。然而,有时我们可能会不小心删除了某些重要的文件,让我们感到惋惜和困惑。删除的文件能恢复吗?别担心,删除并不等于永别,我们也…

CentOS 停服后,服务器 OS 路在何方?

2024 年 6 月 30 日,CentOS Linux 7 终止其生命周期(EOL),至此 CentOS 全系列版本也已停止维护,属于 CentOS 的时代彻底终结。CentOS 停止维护后,用户将无法获得包括问题修复和功能更新在内的任何软件维护和…

小程序里面使用vant ui中的vant-field组件,如何使得输入框自动获取焦点

//.wxml <van-fieldmodel:value"{{ userName }}"placeholder"请输入学号"focus"{{focusUserName}}"/>// .js this.setData({focusUserName: true});vant-field

MSVC2017+Qt 打包

在环境变量下配置好 QT 和 MSVC 的路径 相关搜索&#xff1a; 找不到msvcp140.dll 1.搜索 Qt 选择在编译器路径下打开 2. Windeployqt 生成打包&#xff0c;正常情况下生成 VC 相关package&#xff0c; 即 msvcp140.dll 等MSVC 相关 但是lz尝试没有生成 解决办法 先将生成…