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

news2024/11/29 4:35:22

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接口的BeanDefinitionRegistryPostProcessor
			String[] 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接口的BeanDefinitionRegistryPostProcessor
			postProcessorNames = 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接口则直接执行执行BeanFactoryPostProcessor
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}

		//--------------------------执行已注册的 BeanFactoryPostProcessor bean------------------------------//
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

		// 跟上面一样
		// 都是先按 实现了PriorityOrdered接口、实现了Ordered接口、其他 的顺序处理 BeanFactoryPostProcessor
		List<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 并注册解析到的bean
		ConfigurationClassParser 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 配置的bean
			this.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的注册。

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

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

相关文章

探秘MySQL——全面了解索引、索引优化规则

文章目录0.什么是索引1.常用索引分类逻辑维度底层数据结构维度物理维度&#xff08;InnoDB&#xff09;2.为什么底层是B树平衡二叉查找树红黑树B树&#xff08;多叉&#xff09;B树&#xff08;多叉&#xff09;3.MySQL索引优化SQL性能分析之explainQ.MySQL如何查看查询是否用到…

fastdfs提高系统连接数

1.操作系统 vi /etc/systemd/system.conf 修改后重启系统。 ulimit -n 验证是否生效。 2.tracker ①docker exec -it trackerID bin/bash ②vi /etc/fdfs/tracker.conf 一般性能测试场景可配置10000 3.storage ①docker exec -it storageID bin/bash ②vi /etc/fdfs/st…

k8s-Pod域名学习总结

k8s-Pod域名学习总结 大纲 k8s内置DNS服务 配置Pod的域名服务 CornDNS配置 默认Pod的域名 自定义Pod的域名 实战需求 1 Pod有自己的域名 2 集群内部的Pod可以通过域名访问其他的Pod 基础准备&#xff1a; 1 k8s 集群版本1.17 k8s内置DNS服务 k8s1.17安装完成后自动创建…

保姆级使用PyTorch训练与评估自己的MixMIM网络教程

文章目录前言0. 环境搭建&快速开始1. 数据集制作1.1 标签文件制作1.2 数据集划分1.3 数据集信息文件制作2. 修改参数文件3. 训练4. 评估5. 其他教程前言 项目地址&#xff1a;https://github.com/Fafa-DL/Awesome-Backbones 操作教程&#xff1a;https://www.bilibili.co…

【python】如何用canvas在自己设计的软件上作画

文章目录前言Canvas组件Canvas画布界面画长方体画多边形PhotoImage组件展示gif的图片展示gif法2总结前言 python学习之路任重而道远&#xff0c;要想学完说容易也容易&#xff0c;说难也难。 很多人说python最好学了&#xff0c;但扪心自问&#xff0c;你会用python做什么了&a…

数据结构——线性数据结构(C语言实现顺序表详解)

1.什么线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串等… 在讲顺序表之前&#xff0c;我们先大致了解一下线性表。…

qt .pro文件 qmake编译过程

#&#xff1a;注释一行 QT&#xff1a;此项目中使用的Qt modules列表 CONFIG&#xff1a;此项目中使用的配置选项 TARGET&#xff1a;目标输出文件的名字 TEMPLATE&#xff1a;当生成二进制文件时项目的模版&#xff0c;例如app,lib 平台下使用 windows { SOURCES SysInf…

剑指 Offer II 024. 反转链表

题目链接 剑指 Offer II 024. 反转链表 easy 题目描述 给定单链表的头节点 head&#xff0c;请反转链表&#xff0c;并返回反转后的链表的头节点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&#xff1a;h…

不用费劲,这5款效率工具为你解决学习工作烦恼

今天我要向大家推荐5款超级好用的效率软件&#xff0c;无论是在学习还是办公中都能够极大地提高效率。这些软件可以帮助你解决许多问题&#xff0c;而且每个都是真正的神器。 1.键盘仿真鼠标——NeatMouse NeatMouse 是一个小型的工具能够使用鼠标光标控制指针。当你的鼠标不…

python编程基础

python编程基础 1、什么是编程语言&#xff1f; 编程语言是用来控制计算机的一系列指令&#xff08;Instruction&#xff09;&#xff0c;它有固定的格式和词汇&#xff08;不同编程语言的格式和词汇不一样&#xff09;&#xff0c;必须遵守&#xff0c;否则就会出错&#xf…

opencv加水印

本文介绍opencv给图片加水印的方法。 目录1、添加水印1.1、铺满1.2、在指定区域添加1.3、一比一铺满1、添加水印 添加水印的原理是调低两张图片的透明度&#xff0c;然后叠加起来。公式如下&#xff1a; dst src1 * opacity src2 * (1 - opacity) gamma; opacity是透明度&a…

UE官方教程笔记02-实时渲染基础下

对官方教程视频[官方培训]02-实时渲染基础下 | 陈拓 Epic的笔记没听懂的地方就瞎写反射实时渲染中反射是一个非常有挑战的特性UE中有多种不同的方案&#xff0c;各有各的优势和缺点反射捕获屏幕空间反射平面反射LumenRT Reflection反射捕获在指定位置捕获一张Cube Map需要预计算…

低代码:助力乡村振兴事业开启“智慧模式”

伴随着脱贫攻坚目标任务的全面完成&#xff0c;我国“三农”工作重心历史性地转向全面推进乡村振兴&#xff0c;这也标志着我国农业农村工作迈上了一个新台阶。 什么是乡村振兴&#xff1f; 乡村振兴是新时代“三农”工作的总抓手&#xff0c;坚持农业农村优先发展&#xff0c;…

windows 下docker 安装clickhouse

docker 下载https://www.docker.com/products/docker-desktop/将下载下来的Docker Desktop Installer.exe文件双击进行安装即可&#xff0c;安装完成后&#xff0c;任务栏会出现一个蓝色的小鲸鱼图标&#xff08;注意安装完成后可能会重启系统&#xff09;Docker Desktop如果出…

禁用XXE处理漫谈

前言 近期准备面试题时&#xff0c;XXE漏洞防范措施&#xff08;或者说修复方式&#xff09;在一些文章中比较简略&#xff0c; 故本文根据研究进行总结&#xff0c;作为技术漫谈罢了。 简述 XXE漏洞 XXE&#xff08;XML外部实体注入&#xff09;&#xff0c;程序解析XML数…

国产8K摄像机拍摄回顾与画面数据反馈

本文分析两款国产8K摄像机&#xff0c;一款是全画幅&#xff0c;一款是M43画幅。一、全新国产全画幅8K B1机器参数数据汇总&#xff1a;全画幅8K 60fps&#xff0c;受益于8K全画幅的优势与大幅升级的图像处理系统&#xff0c;BOSMA 8K摄像机系统提升到新的高度。拍摄支持&#…

Ubuntu 安装 CUDA and Cudnn

文章目录0 查看 nvidia驱动版本1 下载Cuda2 下载cudnn参考&#xff1a;0 查看 nvidia驱动版本 nvidia-smi1 下载Cuda 安装之前先安装 gcc g gdb 官方&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive&#xff0c;与驱动版本进行对应&#xff0c;我这里是12.0…

C++语法规则1(C++面向对象 )

C面向对象 面向对象的三大特征是继承&#xff0c;多态和封装&#xff0c;C重面向对象重要的就是这些&#xff0c;我们下面通过一些简单的实例加以理解&#xff0c;从这小节开始&#xff0c;我们将开启新的编程旅途。与 C 语言编程的思想完全不同了&#xff0c;这就是 C!理解概…

[Linux基础]history相关的环境变量设置

目录 背景 简介 命令操作 1. 语法&#xff1a; 2. 功能 3. 参数 环境变量设置 背景 工作中时常收到客户的反馈&#xff0c;我的系统什么也没干&#xff0c;就出现文件丢失&#xff0c;程序错误等等问题&#xff1b;我们在问题排查的时候查看history信息也是重要环节…

大数据导论、Apache ZooKeeper

目录标题1、数据与数据分析2、数据分析基本步骤3、大数据时代4、分布式技术5、 Apache ZooKeeper5.1 ZooKeeper 概述5.2 ZooKeeper 特性5.3 ZooKeeper 集群角色5.4 ZooKeeper 集群搭建5.5 ZooKeeper 数据模型1、数据与数据分析 数据分析是指用适当的统计分析方法对收集来的数据…