Spring实例化源码解析(二)

ConfigurationClassPostProcessor源码 解析

书接上回,在第一次调用invokeBeanDefinitionRegistryPostProcessors方法的时候参数currentRegistryProcessors为ConfigurationClassPostProcessor,本章主要深入这个类的postProcessBeanDefinitionRegistry方法。

postProcessBeanDefinitionRegistry

首先看到的这句注释就能完整的概括这章源码的全部内容:从注册表中的配置类派生更多的bean definitions。刚看这句话可能理解不出来里面的意思,那就各位看官里面请。

	/*** Derive further bean definitions from the configuration classes in the registry.*/@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {int registryId = System.identityHashCode(registry);if (this.registriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);}if (this.factoriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);}this.registriesPostProcessed.add(registryId);processConfigBeanDefinitions(registry);}

首先为BeanDefinitionRegistry生成了一个registryId,这个就是为了检验重复加载的问题,所以前面的都只是校验,如果只看主流程这些你都可以不看,直接关注最后一行代码,processConfigBeanDefinitions(registry)。

在这里插入图片描述

上面这张图可以知道registry中的具体属性内容,需要我们关注的是BeanDefinitionNames和beanPostProcessors这两个。aopConfig是我启动的时候的配置类,也就是我手动注册的类(register(componentClasses)),其他的都是spring自带的,本章的ConfigurationClassPostProcessor就是。

processConfigBeanDefinitions

开始进入正题,接下来就慢慢的拆解processConfigBeanDefinitions方法。

		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();// 当前BeanFacoty中存在的BeanDefinitonNamesString[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {// 根据名称获取BeanDefinitionBeanDefinition beanDef = registry.getBeanDefinition(beanName);if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}// 检测配置候选人的条件,此处为true才会加入配置候选人// private static final Set<String> candidateIndicators = new HashSet<>(8);////	static {//		candidateIndicators.add(Component.class.getName());//		candidateIndicators.add(ComponentScan.class.getName());//		candidateIndicators.add(Import.class.getName());//		candidateIndicators.add(ImportResource.class.getName());//	}// 也就是说只要包含这些也都是配置候选人else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {return;}

首先定义了一个configCandidates的集合,我们把他叫做后续按配置类集合,存放的是BeanDefinitionHolder,BeanDefinitionHolder和BeanDefinition是Spring框架中两个相关的概念,它们之间存在一种包含关系。

BeanDefinition是一个接口,用于描述一个bean的定义信息,包括bean的类名、作用域、依赖关系、属性值等信息。它是Spring框架中定义bean的元数据的核心表示形式。

BeanDefinitionHolder是一个包装类,用于持有BeanDefinition对象以及与之关联的bean名称。它包含两个主要成员变量:bean名称和BeanDefinition对象。

在Spring框架的内部,BeanDefinitionHolder常用于在注册表中存储和管理bean的定义。它提供了一个包装机制,使得可以将bean名称与其对应的BeanDefinition关联起来,并一起存储在注册表中。

通过BeanDefinitionHolder,可以轻松地访问和操作与特定bean相关联的BeanDefinition。它提供了一种方便的方式来获取bean的名称、获取和设置BeanDefinition的属性值等操作。

candidateNames是一个string数组,从上面的截图可以看出,当前会有5个BeanDefinitionNames,然后进入for循环,for循环要做的事情就是把这些BeanDefinition拿出来判断是否是候选配置类。

else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}

ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)方法就是用来判断是否是候选类,可以看出configCandidates.add方法就只在这里用到了。

public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {String className = beanDef.getBeanClassName();if (className == null || beanDef.getFactoryMethodName() != null) {return false;}AnnotationMetadata metadata;if (beanDef instanceof AnnotatedBeanDefinition &&className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {// Can reuse the pre-parsed metadata from the given BeanDefinition...metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();}else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {// Check already loaded Class if present...// since we possibly can't even load the class file for this Class.Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||BeanPostProcessor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass) ||EventListenerFactory.class.isAssignableFrom(beanClass)) {return false;}metadata = AnnotationMetadata.introspect(beanClass);}else {try {MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);metadata = metadataReader.getAnnotationMetadata();}catch (IOException ex) {if (logger.isDebugEnabled()) {logger.debug("Could not find class file for introspecting configuration annotations: " +className, ex);}return false;}}Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);}else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);}else {return false;}// It's a full or lite configuration candidate... Let's determine the order value, if any.Integer order = getOrder(metadata);if (order != null) {beanDef.setAttribute(ORDER_ATTRIBUTE, order);}return true;}

虽然上述的代码看起来很长,而且杂七杂八的逻辑判断让我们不知道如何下手,这个时候可以打端点调试一下。

在这里插入图片描述

唉哟,beanDef是RootBeanDefinition类型,所以会进入else if的逻辑里面,也就到了我们这段代码的第一个核心逻辑。因为这里有个return,所以在看的时候需要关注一下。

Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||BeanPostProcessor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass) ||EventListenerFactory.class.isAssignableFrom(beanClass)) {return false;}

这段代码用于过滤掉特定类型的bean,不对它们应用后续的处理。

具体来说,改代码中的条件判断用于检查给定的beanClass是否属于以下四个类型之一:

1、BeanFactoryPostProcessor:实现了BeanFactoryPostProcessor接口的类,用于在容器实例化任何其他bean之前对bean工厂进行自定义修改。

2、BeanPostProcessor:实现了BeanPostProcessor接口的类,用于在容器实例化bean时对bean进行自定义处理,例如初始化前后的操作。

3、AopInfrastructureBean:实现了AopInfrastructureBean接口的类,用于定义AOP基础设施bean,如AOP代理工厂等。

4、EventListenerFactory:实现了EventListenerFactory接口的类,用于创建事件监听的工厂。

如果给定的beanClass是上述四个类型之一,那么该代码会返回false,表示不对该类型的bean应用后续的处理。

打端点之后其实可以知道Spring自己注入的那些都会被跳过,唯独就是我们手动registry(AopConfig.class)

在这里插入图片描述

else {try {MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);metadata = metadataReader.getAnnotationMetadata();}catch (IOException ex) {if (logger.isDebugEnabled()) {logger.debug("Could not find class file for introspecting configuration annotations: " +className, ex);}return false;}}Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);}else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);}else {return false;}// It's a full or lite configuration candidate... Let's determine the order value, if any.Integer order = getOrder(metadata);if (order != null) {beanDef.setAttribute(ORDER_ATTRIBUTE, order);}

然后我们可以知道metadataReader就是元数据的读取,然后看是否含有@Configuration注解,我们的AopConfig其实是没有@Configutation注解的。

// 容器启动
AnnotationConfigApplicationContext annotationConfigApplicationContext =new AnnotationConfigApplicationContext(AopConfig.class);// AopCofig类,类似于SpringBoot的Application启动类
@ComponentScan(value = {"com.qhyu.cloud.**"})
public class AopConfig {// 啥也没有
}

所以第二个核心逻辑就是isConfigurationCandidate(matedata),ConfigurationClassUtils类中。

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {// Do not consider an interface or an annotation...if (metadata.isInterface()) {return false;}// Any of the typical annotations found?for (String indicator : candidateIndicators) {if (metadata.isAnnotated(indicator)) {return true;}}// Finally, let's look for @Bean methods...return hasBeanMethods(metadata);}

如果是个接口直接返回false,说明得是类才可以。candidateIndicators是一个静态的全局变量,内容是static静态代码块里加载的。

private static final Set<String> candidateIndicators = new HashSet<>(8);static {candidateIndicators.add(Component.class.getName());candidateIndicators.add(ComponentScan.class.getName());candidateIndicators.add(Import.class.getName());candidateIndicators.add(ImportResource.class.getName());}

很明朗了对吧。就是包含这些注解的,都会被加入到configCandidates配置后端名单中。

跳出方法查看beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);这行代码将一个名为 CONFIGURATION_CLASS_ATTRIBUTE 的属性设置为值 CONFIGURATION_CLASS_LITE。

在Spring中,CONFIGURATION_CLASS_ATTRIBUTE是一个常量,用于指示配置类的类型。它通常用于标识配置类是以何种方式加载和处理的。

CONFIGURATION_CLASS_LITE 和 CONFIGURATION_CLASS_FULL 是两种可能的取值,表示不同类型的配置类:

1、CONFIGURATION_CLASS_LITE:表示轻量级配置类。这种类型的配置类通常是通过@Configuration注解进行标记的,但不包含任何特殊的逻辑处理。它们可能指示简单地定义一些bean的声明,而没有涉及复杂的依赖注入、条件化配置或其他高级功能。轻量级配置类在处理过程中会更加简单和高效。

2、CONFIGURATION_CLASS_FULL:表示完全配置类。这种类型的配置类包含了更多的复杂逻辑和功能,如条件配置、依赖注入、AOP、事件处理等。完全配置类可能使用了更多的Spring功能和特性,需要进行更全面的解析和处理。

重回processConfigBeanDefinitions

去过了最远的地方,还得回到最原来的地方。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {//registry 也就是BeanFactory(ConfigurableListableBeanFactory)// configCandidates Candidates候选人List<BeanDefinitionHolder> configCandidates = new ArrayList<>();// 当前BeanFacoty中存在的BeanDefinitonNamesString[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {// 根据名称获取BeanDefinitionBeanDefinition beanDef = registry.getBeanDefinition(beanName);if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}// 检测配置候选人的条件,此处为true才会加入配置候选人// private static final Set<String> candidateIndicators = new HashSet<>(8);////	static {//		candidateIndicators.add(Component.class.getName());//		candidateIndicators.add(ComponentScan.class.getName());//		candidateIndicators.add(Import.class.getName());//		candidateIndicators.add(ImportResource.class.getName());//	}// 也就是说只要包含这些也都是配置候选人else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {return;}

当ConfigurationClassUtils类的isConfigurationCandidate返回true之后就说明ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)返回true。所以会把当前beanDefinitionHolder加入到配置候选集合中。当然如果配置候选集合为空就直接return了。

接下来这个就是单纯的排序,这边直接跳过,不耽误时间,有兴趣自己深入,或者后续单独讲一期排序。

// Sort by previously determined @Order value, if applicableconfigCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}

排序下面这段代码用于获取并设置组件扫描时使用的Bean名称生成器。

首先定义了一个SingletonBeanRegistry类型的变量sbr并初始化为null。然后,通过检查registry对象是否是SingletonBeanRegistry的实例来确定是否可以进行后续的操作。

如果registry是SingletonBeanRegistry的实例,说明它是一个单例Bean注册表,可以用于获取和设置单例Bean。接下来,讲registry对象强制转换为SingletonBeanRegistry类型,并将其赋值给sbr变量。

代码尝试从sbr中获取名为AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR的单例Bean。这个常量是用于指定配置类的Bean名称生成器的键。如果能够获取到该单例Bean,说明已经为配置类设置了特定的Bean名称生成器。如果generator不为null,代码将获取到的generator赋值给两个成员变量:componentScanBeanNameGenerator和importBeanNameGenerator。这两个成员变量分别用于组件扫描和导入Bean时使用的Bean名称生成器。

通过这段代码,可以实现以下功能:

  • 检查是否有合适的单例Bean注册表可用。
  • 获取配置类的Bean名称生成器。
  • 设置组件扫描和导入Bean时使用的Bean名称生成器。

这样做的目的是为了在Spring的组件扫描和Bean加载过程中使用适当的Bean名称生成器,以确保生成的Bean名称符合预期并与其他组件协调一致。

// Parse each @Configuration classConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 这是候选人Set,去重Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);// 以及解析了的配置类Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());do {StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");// 解析我们的配置类,我们使用AnnotationConfigApplicationContent启动的时候,使用的构造函数进行创建// 也就是加入了@ComponentScan注解,需要根据里面的路径扫描包路径,从而找到所有需要被Spring管理的Bean的beanDefiniton信息// ConfigurationClassParser去解析我们的AopConfig类.这个类只会在启动的时候呗调用一次parser.parse(candidates);// 验证parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// ImportBeanDefinitionRegistrar扫描实现了这个接口的方法// ConfigurationClassParser解析的在这里又读出来,这里需要打断点再观察一下。this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();candidates.clear();if (registry.getBeanDefinitionCount() > candidateNames.length) {String[] newCandidateNames = registry.getBeanDefinitionNames();Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames = newCandidateNames;}}while (!candidates.isEmpty());

这段代码是最最最核心的内容,还会引入一个新的类来解析和处理候选配置类,本章我们主要梳理这段代码的逻辑,深入的源码将在下一章节来分析。

1、创建一个ConfigurationClassParser对象并进行初始化。该对象用于解析候选配置类,并生成相应的配置类对象(ConfigurationClass)和相关的Bean定义。

2、创建一个Set<BeanDefinitionHolder>类型的变量candidates,用于存储候选的Bean定义持有者。这些候选人是从configCandidates中获取的,它是一个包含待解析的候选配置类的集合。

3、创建一个Set<ConfigurationClass>类型的变量alreadyParsed,用于存储已经解析过的配置类。

4、进入循环,直到候选人集合为空。在每次循环迭代中,执行以下操作:
a. 启动一个性能追踪步骤,记录解析过程的性能指标。
b. 调用parser.parse(candidates)方法,解析candidates中的候选配置类。在解析过程中,将根据配置类的内容创建相应的Bean定义。
c. 调用parser.validate()方法,对解析后的配置类进行验证,确保它们符合规范。
d. 获取解析后的配置类集合,并将其与已解析的配置类集合进行比较,筛选出新增的配置类。
e. 如果this.reader为null,则创建一个ConfigurationClassBeanDefinitionReader对象,用于读取模型并基于其内容创建Bean定义。
f. 调用this.reader.loadBeanDefinitions(configClasses)方法,将配置类转换为Bean定义,并注册到Bean定义注册表中。
g. 将已解析的配置类添加到alreadyParsed集合中。
h. 根据新的Bean定义数量更新candidateNames数组,并筛选出新增的候选人Bean定义。
i. 清空candidates集合,准备下一次循环迭代。

整个过程会迭代解析和处理所有的候选配置类,将它们转换为相应的Bean定义,并注册到Bean定义注册表中。这样,这些候选配置类中声明的Bean就可以在应用程序中使用和管理了。

总结

本章就ConfigurationClassPostProcessor的processConfigBeanDefinitions方法做了深入的源码分析,描述了方法内部所完成的spring实例化的过程,具体的在项目启动过程中如何将我们自己定义的需要被spring管理的bean的定义信息放入工厂中,会在下一个章节进行详细的分析。

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

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

相关文章

B树的定义和特点

1.多叉查找树的效率 策略1:m叉查找树中&#xff0c;规定除了根节点外&#xff0c;任何结点至少有[m/2]个分叉&#xff0c;即至少含有[m/2]-1个关键字。策略2:m叉查找树中&#xff0c;规定对于任何一个结点&#xff0c;其所有子树的高度都要相同。 而满足以上两种策略的树被称…

halcon算子2、gray_histo

gray_histo 计算直方图 原形&#xff1a;gray_histo(Regions, Image : : : AbsoluteHisto, RelativeHisto) 功能&#xff1a;计算直方图 参数&#xff1a;Regions&#xff1a;区域&#xff0c;要计算的区域&#xff08;在image上的区域&#xff09; Image &#xff1a;要计算的…

【算法】迷宫问题

文章目录 前言1.迷宫问题求解分步骤求解代码 2.迷宫最短路径求解代码 前言 迷宫问题本质就是一个图的遍历问题&#xff0c;从起点开始不断四个方向探索&#xff0c;直到走到出口&#xff0c;走的过程中我们借助栈记录走过路径的坐标。 栈记录坐标有两方面的作用&#xff0c;一…

Git配置SSH

前言&#xff1a; Git是分布式的代码管理工具&#xff0c;远程的代码管理是基于SSH的&#xff0c;所以要使用远程的Git则需要SSH的配置 温馨提示&#xff1a; 1.查看是否已经有了ssh公钥&#xff1a;cd ~/.ssh 如果没有则不会有此文件夹&#xff0c;有则删除 一、git 配置 &a…

【HarmonyOS】【DevEco Studio】盘点DevEco Studio日志获取途径

【关键词】 DevEco Studio、日志获取 【问题背景】 在收到IDE工单的时候&#xff0c;很多时候开发者出现的问题都需要提供一些日志&#xff0c;然后根据日志分析&#xff0c;那么你知道IDE各种日志的获取方式么&#xff1f;往下看 【获取方法】 一、idea.log获取 IDE界面H…

【数据结构】二叉树的层序遍历(四)

目录 一&#xff0c;层序遍历概念 二&#xff0c;层序遍历的实现 1&#xff0c;层序遍历的实现思路 2&#xff0c;创建队列 Queue.h Queue.c 3&#xff0c;创建二叉树 BTree.h BTree.c 4&#xff0c;层序遍历的实现 一&#xff0c;层序遍历概念 层序遍历&#xff1a;除了先序…

大模型助力企业数据驱动,火山引擎数智平台发布AI助手

9月19日&#xff0c;火山引擎在其举办的“V-Tech数据驱动科技峰会”上宣布&#xff0c;火山引擎数智平台VeDI推出“AI助手”&#xff0c;通过接入人工智能大模型&#xff0c;帮助企业提升数据处理和查询分析的效率。即使是不会写代码的运营人员&#xff0c;和大模型对话也能做好…

基于conda的相关命令

conda 查看python版本环境 打开Anaconda Prompt的命令输入框 查看自己的python版本 conda env list激活相应的python版本(环境&#xff09; conda avtivate python_3.9 若输入以下命令可查看python版本 python -V #注意V是大写安装相应的包 pip install 包名5.查看已安装…

stm32----ADC模数转换

一、ADC介绍 ADC&#xff0c;即模数转换器&#xff0c;它可以将模拟信号转化为数字信号。在stm32种一般有3个ADC&#xff0c;每个ADC有18个通道。 12位ADC是一种逐次逼近型模拟数字转换器&#xff0c;它有多达18个通道&#xff0c;可测量16个外部和两个内部信号源。各个通道的A…

物 理 层

二、物理层 1、物理层的基本概念 物理层的作用:尽可能的屏蔽掉传输媒体和通信手段的差异&#xff0c;使物理层上面的数据链路层感觉不到这些差异&#xff0c;使其只需要考虑如何完成本层的协议和服务 1.1、物理层的主要任务 机械特性&#xff1a;指明接口所用的接线器的形状…

Windows10/11无线网卡WIFI驱动详细下载安装教程

官网下载WIFI驱动 《intel官网》 找到下载Windows 10 and Windows 11* WiFi package drivers 查看详细信息 下载对应操作系统的WIFI驱动 安装驱动&#xff0c;然后重启电脑即可。

掌动智能浅谈UI自动化测试工具的重要性

在现代软件开发中&#xff0c;用户界面(UI)的质量和可靠性对于一个应用的成功至关重要。为了确保应用在各种环境和设备上都能正常运行&#xff0c;开发团队需要进行全面的UI测试。为了提高测试效率和减少人为错误&#xff0c;UI自动化测试工具成为不可或缺的工具。本文将探讨UI…

解决Python中的JSON序列化Bug TypeError: Object of type ‘int64‘ is not JSON serializable

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

uni-app:实现条件判断展示图片(函数判定+三目运算)

一、多条件判断&#xff08;通过函数进行图片展示&#xff09; 效果 代码 在data中定义图片信息和要传递的数据信息&#xff0c;在src中写入函数并携带要传递的数据&#xff0c;通过传递的数据在函数中进行判断&#xff0c;并返回对应的图片信息 <template><view&…

安防监控系统/视频云存储EasyCVR平台视频无法播放是什么原因?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

Hadoop:YARN、MapReduce、Hive操作

目录 分布式计算概述 YARN概述 YARN架构 核心架构 辅助架构 MapReduce 概述 配置相关文件 提交MapReduce到YARN Hive Hive架构 Hive在VMware部署 Hive的启动 数据库操作 数据表操作 内部表操作 外部表操作 数据加载和导出 数据加载LOAD 数据加载 - INSERT SEL…

QT--day3

2> 完成文本编辑器的保存工作 widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }void Widget::on_fontbtn_cl…

【排障记录】扩展坞USB 3.0能用而2.0不能用

一、症状表现 日常使用小米的一个扩展坞连接笔记本&#xff0c;平时用来插U盘&#xff0c;没有什么问题&#xff0c;但是今天插了鼠标键盘&#xff0c;发现根本不识别 二、排查过程 目前的连接结构 笔记本C口→type-C延长线→扩展坞A→设备 1.排查笔记本故障 将键盘鼠标插…

Python灰帽编程——错误异常处理与面向对象

文章目录 错误异常处理与面向对象1. 错误和异常1.1 基本概念1.1.1 Python 异常 1.2 检测&#xff08;捕获&#xff09;异常1.2.1 try except 语句1.2.2 捕获多种异常1.2.3 捕获所有异常 1.3 处理异常1.4 特殊场景1.4.1 with 语句 1.5 脚本完善 2. 内网主机存活检测程序2.1 scap…

QT:使用行编辑器、滑动条、滚动条、进度条、定时器

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QLineEdit> //行编辑器 #include <QSlider> //滑动条 #include <QScrollBar> //滚动条 #include <QProgressBar> //进度条 #include <QTimer> …