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

Spring IOC - ConfigurationClassPostProcessor源码解析

上文提到Spring在Bean扫描过程中,会手动将5个Processor类注册到beanDefinitionMap中,其中ConfigurationClassPostProcessor就是本文将要讲解的内容,该类会在refresh()方法中通过调用invokeBeanFactoryPosstProcessors(beanFactory)被调用。
5个Processor类列表如下:
类名
是否BeanDefinitionRegistryPostProcessor
是否BeanFactoryPostProcessor
是否BeanPostProcessor
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
EventListenerMethodProcessor
DefaultEventListenerFactory
ConfigurationClassPostProcessor中两个核心方法将被调用,其主要作用如下:

方法名

作用

postProcessBeanDefinitionRegistry()

(1)@Conditional注解条件解析

(2)被以下注解标注的类及内部类

@Component、@PropertySources、@PropertySource、@ComponentScans、@ComponentScan、@Import、@ImportResource、@Bean、接口中的@Bean

postProcessBeanFactory()

对full模式的配置类BeanDefinition进行CGLib增强:把CGLib生成的子类型设置到beanDefinition中

postProcessBeanDefinitionRegistry()源码及注释如下:
@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);}
进入processConfigBeanDefinitions(registry)方法,其主体逻辑流程图如下:
其中标红步骤为两个关键步骤:

        1. 通过beanDefinition判断是否为配置类

        Spring专门提供了一个工具类:ConfigurationClassUtils来检查是否为配置类,并标记full、lite模式,逻辑如下:

        2. 解析配置类
doProcessConfigurationClass即为真正的对个注解的解析方法,其逻辑流程图如下:

        具体的源码及详细注释如下:

        1. 主流程processConfigBeanDefinitions方法:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 候选配置类定义列表List<BeanDefinitionHolder> configCandidates = new ArrayList<>();// 获取容器中所有bean定义的名字String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {// 获取bean定义BeanDefinition beanDef = registry.getBeanDefinition(beanName);// org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass// Bean Definition是否已经被标记为配置类(full、lite模式)if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}// 查看bean是否是ConfigurationClass//被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {// 是配置类,则加入到候选列表configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were found// 如果为空,即找不到被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记的类,则立即返回if (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicable// 对需要处理的BeanDefinition排序,值越小越靠前configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application context// 检查是否有自定义的beanName生成器SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {//获取自定义的beanName生成器BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {// 如果spring有自定义的beanName生成器,则重新赋值this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}//如果环境对象为空,则创建新的环境对象if (this.environment == null) {this.environment = new StandardEnvironment();}// 构造一个配置类解析器,用来把bean定义的重要信息提取转化为ConfigurationClass// Parse each @Configuration classConfigurationClassParser 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 {// 解析配置类parser.parse(candidates);// 配置类不能被申明为final(因为CGLIB的限制),除非它声明proxyBeanMethods=false// 在@Configuration类中,@Bean方法的实例必须是可重写的,以适应CGLIBparser.validate();// 获取解析器中解析出的配置类ConfigurationClass: parser.getConfigurationClasses()Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());// 过滤掉已解析的配置类configClasses.removeAll(alreadyParsed);// 构造一个bean定义读取器// 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());}// 读取ConfigurationClass获取衍生bean定义并注册到容器// 核心方法,将完全填充好的ConfigurationClass实例转化为BeanDefinition注册入IOC容器this.reader.loadBeanDefinitions(configClasses);// 加入已解析配置类alreadyParsed.addAll(configClasses);// 清空候选配置类定义列表candidates.clear();// 如果容器中bean定义有新增if (registry.getBeanDefinitionCount() > candidateNames.length) {// 查找出新增的配置类bean定义 startString[] 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());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes// 如果想要在@Configuration类中使用ImportAware接口,需要将ImportRegistry注册为bean。这样,@Configuration类就可以// 使用ImportRegistry来获取导入的类信息if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.// 如果MetadataReaderFactory是由外部提供的,则需要清楚其缓存。但是,如果MetadataReaderFactory是由ApplicationContext共享的,则不需要进行清除,// 因为ApplicationContext会自动清楚共享缓存((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}

        2. 其中检查是否为配置类方法ConfigurationClassUtils.checkConfigurationClassCandidate

//检查给定的BeanDefinition是否是一个配置类的候选者(或一个在配置/组件类中声明的嵌套组件类)//并对其进行相应的标记处理//被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {//获取bean定义信息中的class类名String className = beanDef.getBeanClassName();//如果className为空,或者bean定义信息中的factoryMethod不等于空,那么直接返回if (className == null || beanDef.getFactoryMethodName() != null) {return false;}AnnotationMetadata metadata;//通过注解注入的BeanDefinition都是AnnotatedGenericBeanDefinition,实现了AnnotatedBeanDefinition//Spring内部的BeanDefinition都是RootBeanDefiniton,实现了AbstractBeanDefinition//此处主要用于判断是否是归属于AnnotatedBeanDefinitionif (beanDef instanceof AnnotatedBeanDefinition &&className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {// Can reuse the pre-parsed metadata from the given BeanDefinition...// 从当前bean的定义信息中获取元素metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();}//判断是否是Spring中默认的BeanDefinitionelse 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.//获取当前bean对象的Class对象Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();//如果class实例是下面4种类或者接口的子类,父接口等任何一种情况,直接返回if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||BeanPostProcessor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass) ||EventListenerFactory.class.isAssignableFrom(beanClass)) {return false;}//为给定类创建新的AnnotationMetadatametadata = AnnotationMetadata.introspect(beanClass);}//如果上述两种情况都不符合else {try {//获取className的MetadataReader实例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;}}// 获取Bean Definition的元数据被@Configuration注解标注的属性字典值Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());// 如果bean被@Configuration注解标注,并且proxyBeanMethods属性的值为true,则标注当前配置类为full,即需要代理增强,为一个代理类if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);}// 如果存在,或者isConfigurationCandidate返回true,则标注当前配置类为lite,即不需要代理增强,为一个普通类// 标注了Configuration注解且proxyBeanMethods属性的值为false,则为lite模式// 没有标注Configuration注解,但是标注了Component、ComponentScan、Import、ImportResource、@Bean注解、则为lite模式else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);}// 组件类不存在@Configuration注解,直接返回falseelse {return false;}// 获取配置类的排序顺序,设置到组件定义属性中// It's a full or lite configuration candidate... Let's determine the order value, if any.//Bean Definition是一个标记为full/lite的候选项,如果有order属性就设置order属性Integer order = getOrder(metadata);if (order != null) {//设置bean的order值beanDef.setAttribute(ORDER_ATTRIBUTE, order);}return true;}

        3. 真正干活的方法doProessConfigurationClass

// 通过读取源类的注释、成员和方法来构建完整的ConfigurationClass// 1.处理@Component// 2.处理每个@PropertySource// 3.处理每个@ComponentScan// 4.处理每个Impoit// 5.处理每个@ImportResource// 6.找配置类中@Bean注解的方法(找出Class中存在@Bean标注的方法,加入到ConfigurationClass的beanMethods属性)// 7.找接口中@Bean注解的方法// 8.存在父类则递归处理@Nullableprotected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes first// 递归处理嵌套的成员类processMemberClasses(configClass, sourceClass, filter);}// Process any @PropertySource annotations// 处理@PropertySources和@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");}}// Process any @ComponentScan annotations// 处理@ComponentScans和@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) {// The config class is annotated with @ComponentScan -> perform the scan immediately// 配置类使用了注解@ComponentScan -> 立即执行扫描。Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (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());}}}}// Process any @Import annotations// 处理@Import注解// getImports:递归获取@Import导入的类processImports(configClass, sourceClass, getImports(sourceClass), filter, true);// Process any @ImportResource annotations// 处理@ImportResource注解// 读取locations, reader属性,封装存放在importResources map集合中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);}}// Process individual @Bean methods// 处理@Bean方法Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfaces// 处理接口的默认方法实现,从jdk8开始,接口中的方法可以有自己的默认实现,因此如果这个接口的方法加了@Bean注解,也需要被解析processInterfaces(configClass, sourceClass);// Process superclass, if anyif (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;}

相关文章:

Spring IOC - ConfigurationClassPostProcessor源码解析

上文提到Spring在Bean扫描过程中&#xff0c;会手动将5个Processor类注册到beanDefinitionMap中&#xff0c;其中ConfigurationClassPostProcessor就是本文将要讲解的内容&#xff0c;该类会在refresh()方法中通过调用invokeBeanFactoryPosstProcessors(beanFactory)被调用。 5…...

Android OpenGL ES 2.0入门实践

本文既然是入门实践&#xff0c;就先从简单的2D图形开始&#xff0c;首先&#xff0c;参考两篇官方文档搭建个框架&#xff0c;便于写OpenGL ES相关的代码&#xff1a;构建 OpenGL ES 环境、OpenGL ES 2.0 及更高版本中的投影和相机视图。 先上代码&#xff0c;代码效果如下图…...

sql语句性能进阶必须了解的知识点——索引失效分析

在前面的文章中讲解了sql语句的优化策略 sql语句性能进阶必须了解的知识点——sql语句的优化方案-CSDN博客 sql语句的优化重点还有一处&#xff0c;那就是—— 索引&#xff01;好多sql语句慢的本质原因就是设置的索引失效或者根本没有建立索引&#xff01;今天我们就来总结一…...

ctfhub技能树web题目全解

Rce 文件包含 靶场环境 重点是这个代码&#xff0c;strpos&#xff0c;格式是这样的strpoc&#xff08;1&#xff0c;2&#xff0c;3&#xff09; 1是要搜索的字符串&#xff0c;必须有&#xff1b;2是要查询的字符串&#xff0c;必须有&#xff1b;3是在何处开始查询&#…...

AMD、CMD、UMD是什么?

AMD(Asynchronous Module Definition)、CMD(Common Module Definition)和UMD(Universal Module Definition)是JavaScript模块化规范,用于管理和组织JavaScript代码的模块化加载和依赖管理。 1:AMD(异步模块定义): AMD是由RequireJS提出的模块化规范。它支持异步加载…...

AM@微分方程相关概念@线性微分方程@一阶线性微分方程的通解

文章目录 abstract引言 一般的微分方程常微分方程微分方程的解隐式解通解和特解初始条件初值问题微分方程的积分曲线 线性微分方程一阶线性微分方程一阶齐次和非齐次线性微分方程一阶齐次线性微分方程的解一阶非齐次线性微分方程的解 abstract AM微分方程相关概念线性微分方程…...

基于深度学习的安全帽识别检测系统(python OpenCV yolov5)

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、研究的内容与方法二、基于深度学习的安全帽识别算法2.1 深度学习2.2 算法流程2.3 目标检测算法2.3.1 Faster R-CNN2.3.2 SSD2.3.3 YOLO v3 三 实验与结果分析3.1 实验数据集3.1.1 实验数据集的构建3.1.2 数据…...

Spring源码分析篇一 @Autowired 是怎样完成注入的?究竟是byType还是byName亦两者皆有

1. 五种不同场景下 Autowired 的使用 第一种情况 上下文中只有一个同类型的bean 配置类 package org.example.bean;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;Configuration public class FruitCo…...

Goby 漏洞发布|F5 BIG-IP AJP 身份认证绕过漏洞(CVE-2023-46747)

漏洞名称&#xff1a;F5 BIG-IP AJP 身份认证绕过漏洞&#xff08;CVE-2023-46747&#xff09; English Name&#xff1a;F5 BIG-IP AJP authentication bypass vulnerability (CVE-2023-46747) CVSS core: 10 影响资产数&#xff1a; 307282 漏洞描述&#xff1a; Cisco …...

Vue中watch侦听器用法

watch 需要侦听特定的数据源&#xff0c;并在单独的回调函数中执行副作用 watch第一个参数监听源 watch第二个参数回调函数cb&#xff08;newVal,oldVal&#xff09; watch第三个参数一个options配置项是一个对象{ immediate:true //是否立即调用一次 deep:true //是否开启…...

[算法前沿]--054-大语言模型的学习材料

大语言模型的学习材料 Other Papers If you’re interested in the field of LLM, you may find the above list of milestone papers helpful to explore its history and state-of-the-art. However, each direction of LLM offers a unique set of insights and contribut…...

DWA算法,仿真转为C用于无人机避障

DWA算法&#xff0c;仿真转为C用于无人机避障 链接: 机器人局部避障的动态窗口法(dynamic window approach) 链接: 机器人局部避障的动态窗口法DWA (dynamic window approach)仿真源码详细注释版 链接: 常见路径规划算法代码-Matlab &#xff08;纯代码篇&#xff09; …...

现阶段的主流数据库分别是哪几种?

关系型数据库 1. MySQL数据库 MySQL是最受欢迎的开源SQL数据库管理系统&#xff0c;它由 MySQL AB开发、发布和支持。MySQL AB是一家基于MySQL开发人员的商业公司&#xff0c;它是一家使用了一种成功的商业模式来结合开源价值和方法论的第二代开源公司。MySQL是MySQL AB的注册商…...

“原生感”暴涨311%,这届年轻人不再爱浓妆?丨小红书数据分析

近年来&#xff0c;越来越多美妆博主在社交媒体平台安利“原生感妆容”&#xff0c;即我们所熟知的“伪素颜妆”、“裸妆”、“白开水妆”&#xff0c;显然&#xff0c;追求“原生感”成为当代妆容主流。通过小红书数据分析工具&#xff0c;查看#原生感妆容 话题&#xff0c;近…...

基于深度学习的植物识别算法 - cnn opencv python 计算机竞赛

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 MobileNetV2网络4 损失函数softmax 交叉熵4.1 softmax函数4.2 交叉熵损失函数 5 优化器SGD6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的植物识别算法 ** …...

k8s调度约束

List-Watch Kubernetes 是通过 List-Watch的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦。 List-Watch机制 工作机制&#xff1a;用户通过 kubectl请求给 APIServer 来建立一个 Pod。APIServer会将Pod相关元信息存入 etcd 中…...

面经(面试经验)第一步,从自我介绍开始说起

看到一位同学讲自己的面试步骤和过程&#xff0c;我心有所感&#xff0c;故此想整理下面试的准备工作。以便大家能顺利应对面试&#xff0c;通过面试... 求职应聘找工作&#xff0c;面试是必然的关卡&#xff0c;如今竞争激烈呀&#xff0c;想要得到自己喜欢的工作&#xff0c…...

S/4 HANA 中的 Email Template

1 如何创建Email Template? 没有特定的事务用于创建电子邮件模板,我们可以将其创建为 SE80 事务中的存储库对象,如下所示: 1,选择包(或本地对象)并右键单击。 2,选择“创建”->“更多”->“电子邮件模板” 尽管如此,对于已有的Email Template,可以使用程序…...

\r\n和\n的区别 回车/换行 在不同系统下的区别

文章目录 1 \r\n和\n的区别2 什么是 回车/换行3 \r\n和\n 故事 1 \r\n和\n的区别 \r\n和\n是两个常见的控制字符符号&#xff0c;它们在计算机领域中有着不同的作用和用途。 \r\n在Windows系统中被广泛使用&#xff0c;而\n在Unix和Linux系统中更为常见。 在Windows操作系统中…...

机械应用笔记

1. 螺纹转换头&#xff1a;又名金属塞头&#xff0c;例如M20-M16&#xff1b;适合于大小螺纹转换用&#xff1b; 2. 螺纹分英制和公制&#xff0c;攻丝同样也有英制和公制之分&#xff1b; 3. DB9头制作&#xff0c;M6.5的线&#xff0c;用M6.5的钻头扩线孔&#xff0c;在根…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势

一、WebRTC与智能硬件整合趋势​ 随着物联网和实时通信需求的爆发式增长&#xff0c;WebRTC作为开源实时通信技术&#xff0c;为浏览器与移动应用提供免插件的音视频通信能力&#xff0c;在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能&#xff0c;对实时…...

Qt的学习(二)

1. 创建Hello Word 两种方式&#xff0c;实现helloworld&#xff1a; 1.通过图形化的方式&#xff0c;在界面上创建出一个控件&#xff0c;显示helloworld 2.通过纯代码的方式&#xff0c;通过编写代码&#xff0c;在界面上创建控件&#xff0c; 显示hello world&#xff1b; …...

2025年全国I卷数学压轴题解答

第19题第3问: b b b 使得存在 t t t, 对于任意的 x x x, 5 cos ⁡ x − cos ⁡ ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx−cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min ⁡ t max ⁡ x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…...