当前位置: 首页 > news >正文

spring5源码篇(4)——beanFactoryPostProcessor执行/注解bean的装配

spring-framework 版本:v5.3.19

前面研究了beanDefinition的注册,但也仅仅是注册这一动作。那么在spring容器启动的过程中,是何时/如何装配的?以及装配的bean是如何注入的?
(考虑到xml方式基本不用了以及篇幅问题,所以本篇只看注解方式AnnotationConfigApplicationContext。)

文章目录

  • 何时装配
    • beanFactoryPostProcessor的执行入口
    • ConfigurationClassPostProcessor的注册
  • 如何装配
    • 主流程
    • 解析配置类
    • 加载配置类配置的beanDefinition
  • 总结

何时装配

beanFactoryPostProcessor的执行入口

实际上@Bean、@ComponentScan、@Import配置的bean装配逻辑是在ConfigurationClassPostProcessor实现的。这个类实现了BeanDefinitionRegistryPostProcessor接口,且又因为该接口继承自BeanFactoryPostProcessor,所以ConfigurationClassPostProcessor就是一个BeanFactoryPostProcessor。

BeanDefinitionRegistryPostProcessor 提供了一个回调方法 postProcessBeanDefinitionRegistry(),可以在这个方法中对 BeanDefinition 进行修改或者添加新的 BeanDefinition。

单从装配上来说,把ConfigurationClassPostProcessor认为是BeanDefinitionRegistryPostProcessor会比BeanFactoryPostProcessor更贴切。虽然BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,但是顾名思义BeanDefinitionRegistryPostProcessor在BeanFactoryPostProcessor基础上增加了BeanDefinitionRegistry的功能。

从这一点入手即可知道@Bean、@ComponentScan、@Import配置的bean就是在执行所有的BeanFactoryPostProcessor这一步骤装配的,调用流程如下。
在这里插入图片描述

在实际执行BeanFactoryPostProcessor时,spring容器将具体实现委托给了PostProcessorRegistrationDelegate这个工具类。PostProcessorRegistrationDelegate代码中处理BeanFactoryPostProcessord的代码如下

PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors

public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {Set<String> processedBeans = new HashSet<>();if (beanFactory instanceof BeanDefinitionRegistry) {BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();//----------遍历spring容器当前已有的beanFactoryPostProcessors---------//for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {BeanDefinitionRegistryPostProcessor registryProcessor =(BeanDefinitionRegistryPostProcessor) postProcessor;registryProcessor.postProcessBeanDefinitionRegistry(registry);registryProcessors.add(registryProcessor);}else {regularPostProcessors.add(postProcessor);}}//-----------执行已注册的 BeanDefinitionRegistryPostProcessor bean--------------//List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();//postProcessBeanDefinitionRegistry()方法//首先先执行实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessorString[] postProcessorNames =beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());currentRegistryProcessors.clear();//然后执行实现了Ordered接口的BeanDefinitionRegistryPostProcessorpostProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());currentRegistryProcessors.clear();//最后执行其他未被扫描到的BeanDefinitionRegistryPostProcessor//	因为处理的过程中可能会注册新的BeanDefinitionRegistryPostProcessor//	所以这里用循环确保所有的BeanDefinitionRegistryPostProcessor都会被执行boolean reiterate = true;while (reiterate) {reiterate = false;postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);reiterate = true;}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());currentRegistryProcessors.clear();}// postProcessBeanFactory()方法invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);}else {//没有实现BeanDefinitionRegistry接口则直接执行执行BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);}//--------------------------执行已注册的 BeanFactoryPostProcessor bean------------------------------//String[] postProcessorNames =beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);// 跟上面一样// 都是先按 实现了PriorityOrdered接口、实现了Ordered接口、其他 的顺序处理 BeanFactoryPostProcessorList<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();List<String> orderedPostProcessorNames = new ArrayList<>();List<String> nonOrderedPostProcessorNames = new ArrayList<>();for (String ppName : postProcessorNames) {if (processedBeans.contains(ppName)) {// 跳过已经执行的BeanFactoryPostProcessor}else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));}else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}else {nonOrderedPostProcessorNames.add(ppName);}}sortPostProcessors(priorityOrderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());for (String postProcessorName : orderedPostProcessorNames) {orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}sortPostProcessors(orderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());for (String postProcessorName : nonOrderedPostProcessorNames) {nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);beanFactory.clearMetadataCache();}

这个方法首先会判断beanFactory是否来自BeanDefinitionRegistry,如果不是则直接执行beanFactory.beanFactoryPostProcessors属性指向的容器本身已有的BeanFactoryPostProcessors,如果是则保证先执行所有的BeanDefinitionRegistryPostProcessor(包括BeanDefinition中注册的)后再执行BeanFactoryPostProcessors(包括BeanDefinition中注册的)。两者的执行都按 本身已有的->BeanDefinition中PriorityOrdered修饰的->BeanDefinition中Ordered修饰的->BeanDefinition中其他未被执行的顺序执行

ConfigurationClassPostProcessor的注册

上面展示了spring容器在启动的过程中是如何执行BeanFactoryPostProcessors的。那么问题来了,ConfigurationClassPostProcessor这个BeanFactoryPostProcessors是spring容器自带的还是BeanDefinition中注册的呢?如果是注册的,那是何时注册的呢?
我们首先排除容器自带的,通过debug代码可以发现当执行invokeBeanFactoryPostProcessors方法时,spring容器自带的BeanFactoryPostProcessors即传进来的beanFactoryPostProcessors参数为空。
在这里插入图片描述
既然不是自带的BeanFactoryPostProcessors那就肯定在前面的某一步中,注册了ConfigurationClassPostProcessor这么一个BeanDefinition。
通过debug代码分析可以得知,在执行refresh方法前(甚至在register方法执行前)就已经存在beanName为org.springframework.context.annotation.internalConfigurationAnnotationProcessor的ConfigurationClassPostProcessor bean了。由此可以推断是在AnnotationConfigApplicationContext容器初始化的时候注册的。
在这里插入图片描述
查看初始化流程,可以发现创建reader时会执行AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
在这里插入图片描述
AnnotationConfigUtils.registerAnnotationConfigProcessors中会默认注册一些beanDefinition,ConfigurationClassPostProcessor就是在这里注册的
在这里插入图片描述


如何装配

主流程

接下来看ConfigurationClassPostProcessor的具体注册beanDefinition的逻辑
ConfigurationClassPostProcessor#registerAnnotationConfigProcessors

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();// 遍历 BeanDefinition 找到所有的 Configuration 配置类for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);// 如果CONFIGURATION_CLASS_ATTRIBUTE属性不为空说明已经处理过了,打印日志if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// 没有配置类直接返回if (configCandidates.isEmpty()) {return;}// 配置类按order注解排序configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// beanName生成器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();}// 解析 @Configuration class 并注册解析到的beanConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());do {StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");// 解析Configuration配置类parser.parse(candidates);parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);if (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 注册configurationClass 配置的beanthis.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();// 找出新注册的BeanDefinition再次进入循环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());if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}

说通俗一点就是
1:找到所有配置类
2:解析配置类
3:加载配置类配置的beanDefinition
4:循环23直至没有新的配置类加载

解析配置类

点进去parser.parse(candidates)
在这里插入图片描述
最终会来到ConfigurationClassParser#doProcessConfigurationClass

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// 解析成员类processMemberClasses(configClass, sourceClass, filter);}// 解析 @PropertySource 注解for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// 解析 @ComponentScan 注解Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {for (AnnotationAttributes componentScan : componentScans) {//最终会调用ClassPathBeanDefinitionScanner.doScan()方法实现beanDefinition的注册Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());for (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// 解析 @Import 注解processImports(configClass, sourceClass, getImports(sourceClass), filter, true);// 解析 @ImportResource 注解AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// 解析 @Bean 注解(与@Component不同的是这里不会立即注册到beanDefinitionMap中,是在后续的loadBeanDefinition方法才注册)Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 递归接口的默认实现processInterfaces(configClass, sourceClass);// 递归父类if (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);return sourceClass.getSuperClass();}}return null;}

在这一步会解析配置类的 成员类、@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean 以及递归接口默认实现和递归父类,将解析到的结果封装成ConfigurationClass保存到parse.configurationClasses属性,然后再调用reader.loadBeanDefinitions(configClasses)方法实现beanDefintion的加载。

需要注意的是@ComponentScan会在解析这一步的时先将对应的beanDefinition注册到map中了,而@Import、@Bean注解的beanDefinition是在后续的loadBeanDefinitions才注册到map中的。

加载配置类配置的beanDefinition

ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
在这里插入图片描述
可以看到,@Import和@Bean配置的bean都在这一步注册了。

上图提到了两次@Import注解,但作用是不一样的。第一次如果这个类是被Import进来的则先注册他自身,第二次是注册他Import过来类。不过这一步最主要也是最常用的还是注册了@Bean注解的bean。

总结

在启动AnnotationConfigApplicationContext容器创建reader时会默认注册一些beanDefinition,其中就包括ConfigurationClassPostProcessor。ConfigurationClassPostProcessor又是一个beanFactoryPostProcessor,所以后续refresh中invokeBeanFactoryPostProcessors方法执行所有BeanFactoryPostProcessor的方法时就会调用ConfigurationClassPostProcessor的相关逻辑实现@Bean、@ComponentScan、@Import配置的bean的装配。
实现大致如下
1:找到所有配置类
2:解析配置类(@ComponentScan在这一步注册)
3:加载配置类配置的beanDefinition(@Import,@Bean在这一步注册)
4:循环23直至没有新的配置类加载
最后的最后
@ComponentScan调用ClassPathBeanDefinitionScanner.doScan()
@Bean调用BeanDefinitionRegistry.registerBeanDefinition()
@Import调用ImportBeanDefinitionRegistrar.registerBeanDefinitions()
实现beanDefinition的注册。

相关文章:

spring5源码篇(4)——beanFactoryPostProcessor执行/注解bean的装配

spring-framework 版本&#xff1a;v5.3.19 前面研究了beanDefinition的注册&#xff0c;但也仅仅是注册这一动作。那么在spring容器启动的过程中&#xff0c;是何时/如何装配的&#xff1f;以及装配的bean是如何注入的&#xff1f; &#xff08;考虑到xml方式基本不用了以及篇…...

masstransit的message几个高级用法

1&#xff09;问题&#xff0c;Class MessageA 基类&#xff0c;Class MessageB继承自MessageA&#xff1b; 用bus.Publish方法本想把有些消息只发给B队列&#xff0c;结果由于其继承关系A队列也获得了消息&#xff1b; 解决方法用send&#xff0c; Uri uri new Uri(RabbitM…...

漏洞分析丨cve-2012-0003

作者:黑蛋一、漏洞简介这次漏洞属于堆溢出漏洞&#xff0c;他是MIDI文件中存在的堆溢出漏洞。在IE6&#xff0c;IE7&#xff0c;IE8中都存在这个漏洞。而这个漏洞是Winmm.dll中产生的。二、漏洞环境虚拟机调试工具目标软件辅助工具XP-SP3、KaliOD、IDAIE6Windbg组件gflags.exe三…...

rm命令——删除文件或目录

rm命令是英文单词remove的缩写&#xff0c;主要功能是删除文件或目录。 因为删除文件是一个破坏性动作&#xff0c;因此&#xff0c;在使用时需要格外小心&#xff0c;在执行之前一定要再三确认删除的是哪个目录中的什么文件。 rm命令的语法格式如下&#xff1a; rm [选项] …...

【零基础入门学习Python---Python的基本语法使用】

一.Python基本语法使用 Python是一种易学且功能强大的编程语言,具有简洁的语法和广泛的应用领域。在本文中,我们将介绍Python的基本语法使用,以帮助初学者快速入门Python编程。 1.1 注释 Python 支持两种类型的注释:单行注释和多行注释。 单行注释:以 # 符号开头,从 # …...

数据仓库相关概念的解释

数据仓库相关概念的解释 文章目录数据仓库相关概念的解释1 ETL是什么&#xff1f;ETL体系结构2 数据流向何为数仓DW3 ODS 是什么&#xff1f;4 数据仓库层DWDWD 明细层DWD 轻度汇总层&#xff08;MID或DWB&#xff0c;data warehouse basis&#xff09;DWS 主题层&#xff08;D…...

1/4车、1/2车、整车悬架模糊PID控制仿真合集

目录 前言 1. 1/4悬架系统 1.1数学模型 1.2仿真分析 2. 1/2悬架系统 2.1数学模型 2.2仿真模型 2.3仿真分析 3. 整车悬架系统 3.1数学模型 3.2仿真分析 4.总结 前言 前面几篇文章介绍了LQR、SkyHook、H2/H∞、PID控制&#xff0c;接下来会继续介绍滑模、反步法、M…...

Linux性能补丁升级,避免不必要的跨核Wake-Up

导读一个由英特尔发起的、旨在改进Linux内核公平调度程序代码的补丁系列&#xff0c;也看到了来自AMD工程师和其他利益相关者的测试/反馈&#xff0c;并继续进行改进。这个补丁系列的重点是避免在不必要的情况下发生过多的跨核唤醒(Cross-CPU Wake-up)。这样一来&#xff0c;这…...

Spring Cloud Alibaba全家桶(六)——微服务组件Sentinel介绍与使用

前言 本文小新为大家带来 微服务组件Sentinel介绍与使用 相关知识&#xff0c;具体内容包括分布式系统存在的问题&#xff0c;分布式系统问题的解决方案&#xff0c;Sentinel介绍&#xff0c;Sentinel快速开始&#xff08;包括&#xff1a;API实现Sentinel资源保护&#xff0c;…...

拼多多2021笔试真题集 -- 3. 多多的求和计算

多多的求和计算 多多路上从左到右有N棵树&#xff08;编号1&#xff5e;N&#xff09;&#xff0c;其中第i个颗树有和谐值Ai。 多多鸡认为&#xff0c;如果一段连续的树&#xff0c;它们的和谐值之和可以被M整除&#xff0c;那么这个区间整体看起来就是和谐的。 现在多多鸡想请…...

DP算法:动态规划算法

步骤&#xff08;1&#xff09;确定初始状态&#xff08;2&#xff09;确定转移矩阵&#xff0c;得到每个阶段的状态&#xff0c;由上一阶段推到出来&#xff08;3&#xff09;确定边界条件。例题蓝桥杯——印章&#xff08;python实现&#xff09;使用dp记录状态&#xff0c;d…...

一三四——一六七

一三四、JavaScript——_DOM简介 MDNq前端参考文档&#xff1a;DOM 概述 - Web API 接口参考 | MDN (mozilla.org) 一三五、JavaScript——HelloWorld <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta h…...

day29_JS

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、事件 二、DOM操作 三、案例 零、 复习昨日 js 脚本语言,弱类型 引入方案: 3种 js的内容: 语法dombom 语法 变量 var 数据类型 引用类型 - 对象,J…...

【HTTP协议与Web服务器】

HTTP协议与Web服务器浏览器与服务器通信过程HTTP的请求报头HTTP请求报头结构HTTP的请求方法HTTP应答报头HTTP应答报头结构应答状态web服务器的c语言实现浏览器与服务器通信过程 浏览器与Web服务器再应用层通信使用的是HTTP协议&#xff0c;而HTTP协议在传输层使用的是TCP协议。…...

Idea+maven+spring-cloud项目搭建系列--12 整合grpc

前言&#xff1a; grpc 是geogle 开源的rpc 通信框架&#xff0c;通过定义proto生成通信存根&#xff0c;像本地调用服务一样&#xff0c;进行远程服务的调用&#xff1b; 1 消费端服务提供&#xff1a; 1.1 引入grpc 和 protobuf <!-- RPC --> <!-- RPC 服务调用 …...

Revit开洞问题:结构专业开洞口剖面显示及一键开洞

一、Revit中关于结构专业开洞口剖面显示问题 Revit作业的时候&#xff0c;我们不仅只为了一个最后的三维立体模型,我们需要的是一个符合国家以及本院制图标准的一个出图样式,这时候就会出现各种各样的显示问题&#xff0c;本期就一个结构专业开洞显示问题&#xff0c;跟大家一起…...

0107连通分量-无向图-数据结构和算法(Java)

文章目录1 API2 代码实现和分析测试后记1 API 深度优先搜索下一个直接应用就是找出一幅图中的连通分量,定义如下API。 public class CCCC(Graph g)预处理构造函数booleanconnected(int v, int w)v和w连通吗intcount()连通分量数intid(int v)v所在的连通分量标识符(0~count()-…...

[学习笔记]黑马程序员python教程

文章目录思维导图Python基础知识图谱面向对象SQL入门和实战Python高阶技巧第一阶段第九章&#xff1a;Python异常、模块与包1.9.1异常的捕获1.9.1.1 为什么要捕获异常1.9.1.2 捕获常规的异常1.9.1.3 捕获指定的异常1.9.1.4 捕获多个异常1.9.1.5 捕获全部异常1.9.1.6 异常的else…...

如何配置用于构建 FastReport Online Designer 的 API ?

FastReport Online Designer 是一个跨平台的报表设计器&#xff0c;允许通过任何平台的移动设备创建和编辑报表。今天我们就一起来看看在2023版中新增和改进的功能有哪些&#xff0c;点击下方可以获取最新版免费试用哦&#xff01; FastReport Onlin Designe最新版试用https:/…...

【嵌入式Linux内核驱动】02_字符设备驱动

字符设备驱动 〇、基本知识 设备驱动分类 &#xff08;按共性分类方便管理&#xff09; 1.字符设备驱动 字符设备指那些必须按字节流传输&#xff0c;以串行顺序依次进行访问的设备。它们是我们日常最常见的驱动了&#xff0c;像鼠标、键盘、打印机、触摸屏&#xff0c;还有…...

【零散整理】

1-1 git查看代码的项目总行数 git log --prettytformat: --numstat | awk ‘{ add $1; subs $2; loc $1 - $2 } END { printf “added lines: %s, removed lines: %s, total lines: %s\n”, add, subs, loc }’ - 1-2 cookie const cookies document.cookie.split(; )for…...

RocketMQ重复消费的症状以及解决方案

RocketMQ重复消费的症状以及解决方案 生产消息时重复 症状 当一条消息已被成功发送到 消费者 并完成持久化&#xff0c;此时出现了网络闪断或者客户端宕机&#xff0c;导致服务端对客户端应答失败。 如果此时 生产者 意识到消息发送失败并尝试再次发送消息&#xff0c;消费者…...

数字化时代,企业的商业模式建设

随着新一代信息化、数字化技术的应用&#xff0c;众多领域通过科技革命和产业革命实现了深度化的数字改造&#xff0c;进入到以数据为核心驱动力的&#xff0c;全新的数据处理时代&#xff0c;并通过业务系统、商业智能BI等数字化技术和应用实现了数据价值&#xff0c;从数字经…...

项目实战典型案例23——-注册上nacos上的部分服务总是出现频繁掉线的情况

注册上nacos上的部分服务总是出现频繁掉线的情况一&#xff1a;背景介绍二&#xff1a;思路&方案解决问题过程涉及到的知识nacos服务注册和服务发现一&#xff1a;背景介绍 spring cloud项目通过nacos作为服务中心和配置中心&#xff0c;出现的问题是其中几个服务总是出现…...

玩转金山文档 3分钟让你的文档智能化

在上个月底&#xff0c;我们给大家推荐了金山轻维表的几个使用场景&#xff0c;社群中不少用户反响很好&#xff0c;对其中一些场景的解决方案十分感兴趣。但也有一些人表示&#xff0c;有些场景不知道如何实现&#xff0c;希望我们能提供模版/教程。这次我们将做一期热门模板盘…...

安装了nodejs怎么安装nvm

第一步&#xff0c;从控制面板卸载已经安装的node 第二步&#xff0c;删除C盘program开头文件夹下的node文件 第三步&#xff0c;去C/user/用户名 文件夹下&#xff0c;删除.npmrc文件 第四步&#xff0c;打开隐藏文件&#xff0c;第三步文件夹下有一个Appdata文件&#xff…...

java安全编码规范考试

java安全编码规范考试 整理不易&#xff0c;收点币&#xff01;&#xff01; 安全编码规范考试.md 下面对zip文件的安全解压缩描述&#xff0c;错误的是 A.zip文件解压时&#xff0c;可以使用entry.getSize(&#xff09;对解压缩文件进行文件大小判断 B.zip文件解压时&…...

表格检测识别技术的发展历程

近年来&#xff0c;随着计算机技术的飞速发展&#xff0c;越来越多的研究者开始关注表格检测识别技术。表格检测识别技术是一种利用计算机自动处理表格的技术&#xff0c;它可以实现从文本中检测出表格&#xff0c;并进行识别和提取。这种技术有助于提高文本处理的效率&#xf…...

设计UI - Adobe xd对象介绍

矩形工具 新建矩形 操作步骤&#xff1a;选择矩形工具&#xff0c;快捷键R&#xff0c;鼠标在画板上拖出矩形即可。 拖动定界框周围圆形手柄&#xff0c;可快速调整矩形大小&#xff0c;也可以输入宽和高的参数对矩形大小进行改变。 移动矩形 操作步骤&#xff1a;选择选择工具…...

优思学院|精益生产中的“单件流”真的能够做到吗?

精益生产中提到的“一个流”&#xff08;One Piece Flow&#xff09;是一种生产方式&#xff0c;它的核心理念是通过合理配置作业场地、人员和设备&#xff0c;使产品从投入到成品产出的整个制造加工过程中始终处于不停滞、不堆积、不超越&#xff0c;按节拍一个一个地流动。 …...