spring源码解析-(2)Bean的包扫描

news2024/11/26 12:16:26

包扫描的过程

测试代码:

// 扫描指定包下的所有类
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
// 扫描指定包下的所有类
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
scanner.scan("com.test.entity");
for (String beanDefinitionName : registry.getBeanDefinitionNames()) {
    System.err.println(beanDefinitionName);
}

注意:下述源码在阅读时应尽量避免死磕,先梳理整体流程,理解其动作的含义,具体细节可以在看完整体之后再细致打磨。如果整个流程因为一些细节而卡住,那就丧失了看源码的意义,我们需要的是掌握流程和优秀的设计理念,而不是一字一句的抄下来。

scan方法详解

源码如下:翻译通过CodeGeeX进行自动生成并自己进行修改。

public int scan(String... basePackages) {
    // 获取扫描开始时的BeanDefinition数量
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    // 执行扫描
    doScan(basePackages);
    // 如果需要,注册注解配置处理器
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    // 返回扫描结束时的BeanDefinition数量与扫描开始时的数量之差
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

通过上面的代码,我们可以看到核心的方法为doScan,具体源码如下:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 确保至少传入一个基础包
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    // 创建一个存放BeanDefinitionHolder的集合
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        // 1找出候选的组件
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        // 遍历候选的组件
        for (BeanDefinition candidate : candidates) {
            // 解析组件的作用域元数据
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            // 设置组件的作用域
            candidate.setScope(scopeMetadata.getScopeName());
            // 2生成组件的名称
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            // 如果候选的组件是抽象BeanDefinition的实例
            if (candidate instanceof AbstractBeanDefinition) {
                // 执行后处理器,对候选的组件进行处理
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            // 如果候选的组件是带有注解的BeanDefinition的实例
            if (candidate instanceof AnnotatedBeanDefinition) {
                // 3处理带有注解的BeanDefinition的公共定义
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            // 如果检查候选的组件
            if (checkCandidate(beanName, candidate)) {
                // 创建一个BeanDefinitionHolder,并将其添加到集合中
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                // 应用代理模式
                definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 将BeanDefinition注册到容器中
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    // 返回存放BeanDefinitionHolder的集合
    return beanDefinitions;
}

现在我们一步步的使用debug查看整个doScan的大体过程

1. 通过传入的路径扫描并得到对应的BeanDefinition

截取的部分代码片段如下:

for (String basePackage : basePackages) {
    // 找出候选的组件
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    // other
}

findCandidateComponents方法的源码:

从 Spring 5.0 开始新增了一个 @Indexed 注解(新特性,@Component 注解上面就添加了 @Indexed 注解), 这里不会去扫描指定路径下的 .class 文件,而是读取所有 META-INF/spring.components 文件中符合条件的类名,直接添加 .class 后缀就是编译文件,而不要去扫描。

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // 如果componentsIndex不为空,并且indexSupportsIncludeFilters()为true
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        // 从componentsIndex中添加候选组件
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    // 否则
    else {
        // 扫描候选组件
        return scanCandidateComponents(basePackage);
    }
}

我们着重看scanCandidateComponents方法,源码如下:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    // 创建一个LinkedHashSet,用于存储扫描到的候选组件
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        // 1.1拼接包搜索路径
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        // 1.2获取资源
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        // 判断是否开启日志记录
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            try {
                // 1.3获取元数据读取器
                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                // 判断是否是候选组件
                if (isCandidateComponent(metadataReader)) {
                    // 1.4创建一个ScannedGenericBeanDefinition,用于存储元数据读取器
                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                    sbd.setSource(resource);
                    // 判断是否是候选组件
                    if (isCandidateComponent(sbd)) {
                        if (debugEnabled) {
                            logger.debug("Identified candidate component class: " + resource);
                        }
                        // 将候选组件添加到candidates中
                        candidates.add(sbd);
                    } else {
                        if (debugEnabled) {
                            logger.debug("Ignored because not a concrete top-level class: " + resource);
                        }
                    }
                } else {
                    if (traceEnabled) {
                        logger.trace("Ignored because not matching any filter: " + resource);
                    }
                }
            } catch (FileNotFoundException ex) {
                if (traceEnabled) {
                    logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
                }
            } catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to read candidate component class: " + resource, ex);
            }
        }
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

1.1 拼接搜索路径

源码如下:

String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;

简单的字符串拼接,其代码本身没有任何技术含量,但debug的时候,可以通过其显示的值来帮助我们更好的理解项目。

在这里插入图片描述

debug的结果为:classpath*:com/test/entity/**/*.class

可以看到,是从根路径的com.test.enetity下开始寻找所有的字节码文件。

1.2 根据包扫描路径获取资源

Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

具体方法:

public Resource[] getResources(String locationPattern) throws IOException {
    // 断言locationPattern不能为空
    Assert.notNull(locationPattern, "Location pattern must not be null");
    // 如果locationPattern以CLASSPATH_ALL_URL_PREFIX开头
    if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
        // 是一个类路径资源(同一个名称可能存在多个资源)
        if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
            // 是一个类路径资源模式
            return findPathMatchingResources(locationPattern);
        }
        else {
            // 所有具有给定名称的类路径资源
            return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
        }
    }
    else {
        // 通常只在后缀前加上前缀,
        // 并且在Tomcat中,只有在“war:”协议下,才会使用“*/”分隔符。
        int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
                locationPattern.indexOf(':') + 1);
        if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
            // 是一个文件模式
            return findPathMatchingResources(locationPattern);
        }
        else {
            // 具有给定名称的单个资源
            return new Resource[] {getResourceLoader().getResource(locationPattern)};
        }
    }
}

接着我们进入到findPathMatchingResources的方法中,具体源码如下:

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    // 确定根目录路径
    String rootDirPath = determineRootDir(locationPattern);
    // 获取locationPattern中根目录路径之后的部分
    String subPattern = locationPattern.substring(rootDirPath.length());
    // 获取根目录资源
    Resource[] rootDirResources = getResources(rootDirPath);
    // 创建一个LinkedHashSet集合
    Set<Resource> result = new LinkedHashSet<>(16);
    // 遍历根目录资源
    for (Resource rootDirResource : rootDirResources) {
        // 解析根目录资源
        rootDirResource = resolveRootDirResource(rootDirResource);
        // 获取根目录资源的URL
        URL rootDirUrl = rootDirResource.getURL();
        // 如果存在equinoxResolveMethod且根目录URL的协议以"bundle"开头
        if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
            // 调用equinoxResolveMethod方法,获取解析后的URL
            URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
            // 如果解析后的URL不为空
            if (resolvedUrl != null) {
                // 将解析后的URL赋值给rootDirUrl
                rootDirUrl = resolvedUrl;
            }
            // 将解析后的URL封装为Resource
            rootDirResource = new UrlResource(rootDirUrl);
        }
        // 如果根目录URL的协议以"vfs"开头
        if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
            // 调用VfsResourceMatchingDelegate的findMatchingResources方法,获取匹配的资源
            result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
        }
        // 如果根目录URL是jar文件或者根目录资源是jar资源
        else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
            // 调用doFindPathMatchingJarResources方法,获取匹配的jar资源
            result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
        }
        // 否则
        else {
            // 调用doFindPathMatchingFileResources方法,获取匹配的文件资源
            result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
        }
    }
    // 如果存在日志日志功能
    if (logger.isTraceEnabled()) {
        // 打印日志
        logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
    }
    // 返回结果
    return result.toArray(new Resource[0]);
}

1.3 获取元数据

源码:可以看到通过1.2中获取到的资源将通过如下方法获取一个名为MetadataReader的对象,那么这个元数据读取器究竟是干什么的?

// getMetadataReaderFactory工厂模式构建MetadataReader
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

MetadataReader接口中只定义了三个方法,源码如下:

public interface MetadataReader {
	Resource getResource();
	ClassMetadata getClassMetadata();
	AnnotationMetadata getAnnotationMetadata();
}

根据debug追随到的源码,发现其进入到了MetadataReader的实现类SimpleMetadataReader中,其构造器如下:

SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
    // 这里使用到了经典的访问者模式,可以自行了解一下
    SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
    getClassReader(resource).accept(visitor, PARSING_OPTIONS);
    this.resource = resource;
    this.annotationMetadata = visitor.getMetadata();
}

感兴趣的可以着重看下getClassReader(resource).accept(visitor, PARSING_OPTIONS);这句话中大概做了那些事情。

具体源码就不展示,这里大概就是将class文件读取并操作二进制代码。(可以通过《深入了解Java虚拟机》这本书来了解一下java的字节码相关内容)

1.4 创建ScannedGenericBeanDefinition

通过构造器的方式将MetadataReader中读取到的内容放入BeanDefinition中。

public ScannedGenericBeanDefinition(MetadataReader metadataReader) {
    Assert.notNull(metadataReader, "MetadataReader must not be null");
    this.metadata = metadataReader.getAnnotationMetadata();
    setBeanClassName(this.metadata.getClassName());
    setResource(metadataReader.getResource());
}

2. 获取Bean的名称

String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

我们可以看一下spring是如何获取bean的名称的

public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    if (definition instanceof AnnotatedBeanDefinition) {
        String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
        if (StringUtils.hasText(beanName)) {
            // Explicit bean name found.
            return beanName;
        }
    }
    // Fallback: generate a unique default bean name.
    return buildDefaultBeanName(definition, registry);
}

可以看到该方法是有两个分支,如果通过注解中可以拿到名字,则直接返回,具体方法如下:

// 确定基于注解的bean名称
protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
    // 获取注解的元数据
    AnnotationMetadata amd = annotatedDef.getMetadata();
    // 获取注解的类型
    Set<String> types = amd.getAnnotationTypes();
    // 初始化bean名称
    String beanName = null;
    // 遍历注解类型
    for (String type : types) {
        // 获取注解的属性
        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
        if (attributes != null) {
            // 获取注解的元注解类型
            Set<String> metaTypes = this.metaAnnotationTypesCache.computeIfAbsent(type, key -> {
                Set<String> result = amd.getMetaAnnotationTypes(key);
                return (result.isEmpty() ? Collections.emptySet() : result);
            });
            // 判断注解是否为stereotype注解,并且包含name和value属性
            if (isStereotypeWithNameValue(type, metaTypes, attributes)) {
                // 获取name属性的值
                Object value = attributes.get("value");
                if (value instanceof String) {
                    String strVal = (String) value;
                    // 判断字符串长度是否大于0
                    if (StringUtils.hasLength(strVal)) {
                        // 判断beanName是否为空,或者与strVal不相等
                        if (beanName != null && !strVal.equals(beanName)) {
                            // 抛出异常
                            throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
                                    "component names: '" + beanName + "' versus '" + strVal + "'");
                        }
                        // 更新beanName
                        beanName = strVal;
                    }
                }
            }
        }
    }
    return beanName;
}

或者,根据类的class直接创建名称,代码如下:

protected String buildDefaultBeanName(BeanDefinition definition) {
    // 获取bean的类名
    String beanClassName = definition.getBeanClassName();
    // 断言bean的类名不能为空
    Assert.state(beanClassName != null, "No bean class name set");
    // 获取bean的简短名称
    String shortClassName = ClassUtils.getShortName(beanClassName);
    // 将简短名称的首字母转换为小写
    return Introspector.decapitalize(shortClassName);
}

3. 处理带有注解的BeanDefinition的公共定义

AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);

在此方法中,我们可以了解到,注册为Bean的类上可以添加的注解有几种,代码如下:

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
    AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
    if (lazy != null) {
        abd.setLazyInit(lazy.getBoolean("value"));
    }
    else if (abd.getMetadata() != metadata) {
        lazy = attributesFor(abd.getMetadata(), Lazy.class);
        if (lazy != null) {
            abd.setLazyInit(lazy.getBoolean("value"));
        }
    }

    if (metadata.isAnnotated(Primary.class.getName())) {
        abd.setPrimary(true);
    }
    AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
    if (dependsOn != null) {
        abd.setDependsOn(dependsOn.getStringArray("value"));
    }

    AnnotationAttributes role = attributesFor(metadata, Role.class);
    if (role != null) {
        abd.setRole(role.getNumber("value").intValue());
    }
    AnnotationAttributes description = attributesFor(metadata, Description.class);
    if (description != null) {
        abd.setDescription(description.getString("value"));
    }
}

4 将定义好的BeanDefinition放入set,并返回set。

if (checkCandidate(beanName, candidate)) {
    // 创建一个BeanDefinitionHolder,并将其添加到集合中
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    // 应用代理模式
    definitionHolder =
            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    beanDefinitions.add(definitionHolder);
    // 将BeanDefinition注册到容器中
    registerBeanDefinition(definitionHolder, this.registry);
}

思考总结:

1、可以看到,在spring中使用了很多设计模式,设计模式在解决一系列问题上非常有帮助,如创建Bean的Bean工厂,访问并根据字节码操作的访问者模式,需要理解其思想,在遇到问题的时候,往设计模式上想一想。后续可能要深入的学习一下设计模式。

2、关于数据类型的使用,其实本人在实际开发的过程中大部分时间都使用list,Map,其中也遇到过很多次内存溢出,在合理的选择数据类型上,有必要仔细斟酌。

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

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

相关文章

SSL/TLS和HTTPS

HTTPS就是用了TLS包装的Socket进行通信的HTTP 混合加密 被称为混合加密。具体过程如下&#xff1a; 使用非对称加密协商对称密钥&#xff1a; 在通信的开始阶段&#xff0c;通常由客户端和服务器使用非对称加密算法&#xff08;如RSA&#xff09;来协商一个对称密钥。通常情…

2024年全国大学生数据统计与分析竞赛A题论文和代码:抖音用户评论数据统计与情感分析模型

2024年全国大学生数据统计与分析竞赛A题论文和代码已完成&#xff0c;代码为B题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求解&#x…

【JavaScript】内置对象 - 字符串对象 ④ ( 根据索引位置返回字符串中的字符 | 代码示例 )

文章目录 一、根据索引位置返回字符串中的字符1、charAt 函数获取字符2、charCodeAt 函数获取字符 ASCII 码3、数组下标获取字符 String 字符串对象参考文档 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String 一、根据索引位置返回…

Oracle的优化器

sql优化第一步&#xff1a;搞懂Oracle中的SQL的执行过程 从图中我们可以看出SQL语句在Oracle中经历了以下的几个步骤&#xff1a; 语法检查&#xff1a;检查SQL拼写是否正确&#xff0c;如果不正确&#xff0c;Oracle会报语法错误。 语义检查&#xff1a;检查SQL中的访问对象…

语法分析!!!

一、实验题目 根据给定文法编写调试预测分析程序&#xff0c;对任意输入串用预测分析法进行语法分析。 二、实验目的 加深对预测分析法的理解。 三、实验内容 四、实验代码 #include <iostream> #include <stdio.h> #include <string> #include <…

鸿蒙? 车载?Flutter? React Native? 为什么我劝你三思,说点不一样的

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 引言 当今信息技术领域日新月异&#xff0c;各种新技术和新平台层出不穷。鸿蒙&#xff08;HarmonyOS&#xff09;、Flutter、以及车载应用开发…

Cell-在十字花科植物中年生和多次开花多年生开花行为的互相转化-文献精读21

Reciprocal conversion between annual and polycarpic perennial flowering behavior in the Brassicaceae 在十字花科植物中年生和多次开花多年生开花行为的互相转化 亮点 喜马拉雅须弥芥 和 内华达糖芥 是两个多年生植物模型 MADS-box 基因的剂量效应决定了一年生、二年生…

树莓派4B 零起点(一) 树莓派 无屏 从购买到启动

目录 背景 一. 准备工作 二、烧录系统 三、连接系统 背景 准备开发ROS机器人&#xff0c;在淘宝上购买的树莓派4B(4G)到货了&#xff0c;配件都很齐全&#xff0c;那么就直接开箱验货。 一. 准备工作 1 、硬件&#xff1a;(如下图) (我的购买链接: 树莓派4B 4g 套件) 2…

信号:干扰类别及特征提取(二)

目录 第二部分&#xff1a;特征提取 一&#xff1a;瞬时特征参数 1.零中心归一化瞬时幅度之谱密度的最大值 2.非弱信号段零中心归一化瞬时幅度的标准偏差 3.零中心归一化瞬时幅度绝对值的标准偏差 4.零中心归一化非弱信号段瞬时频率的标准偏差 5.零中心归一化非弱信号段…

OpenCV学习 基础图像操作(十七):泛洪与分水岭算法

原理 泛洪填充算法和分水岭算法是图像处理中的两种重要算法&#xff0c;主要用于区域分割&#xff0c;但它们的原理和应用场景有所不同&#xff0c;但是他们的基础思想都是基于区域迭代实现的区域之间的划分。 泛洪算法 泛洪填充算法&#xff08;Flood Fill&#xff09;是一…

【Autopilot】没有自动添加本地管理员的问题处理

【问题】某公司选用了D记的笔记本电脑&#xff0c;约定出厂就预配置好Autopilot&#xff0c;当时向D记提供了三个信息&#xff1a; 1. M365的租户ID 2. 公司域名信息 3. Group Tag (某公司为跨国公司&#xff0c;通过Group Tag来区分国家&#xff0c;比如CHN-中国&#xff0c;L…

【模拟-BM99 顺时针旋转矩阵】

题目 BM99 顺时针旋转矩阵 描述 有一个NxN整数矩阵&#xff0c;请编写一个算法&#xff0c;将矩阵顺时针旋转90度。 给定一个NxN的矩阵&#xff0c;和矩阵的阶数N,请返回旋转后的NxN矩阵。 分析 模拟&#xff0c;写几个样例&#xff0c;分析一下新矩阵元素下标与原矩阵元素…

前端工程化:基于Vue.js 3.0的设计与实践

这里写目录标题 《前端工程化&#xff1a;基于Vue.js 3.0的设计与实践》书籍引言本书概述主要内容作者简介为什么选择这本书&#xff1f;结语 《前端工程化&#xff1a;基于Vue.js 3.0的设计与实践》书籍 够买连接—>https://item.jd.com/13952512.html 引言 在前端技术日…

17、matlab实现均值滤波、中值滤波、Butterworth滤波和线性相位FIR滤波

1、创建信号 1&#xff09;创建正余弦信号、噪声信号和混合信号 原始正余弦信号公式&#xff1a;Signal1 sin(2*pi*20* t) sin(2*pi*40* t) sin(2*pi*60* t) 高斯分布的白噪声&#xff1a;NoiseGauss [randn(1,2000)] 均匀分布的白噪声&#xff1a;[rand(1,2000)] 正余弦…

k8s学习--kubernetes服务自动伸缩之水平收缩(pod副本收缩)HPA详细解释与案例应用

文章目录 前言HPA简介简单理解详细解释HPA 的工作原理监控系统负载模式HPA 的优势使用 HPA 的注意事项应用类型 应用环境1.metircs-server部署2.HPA演示示例&#xff08;1&#xff09;部署一个服务&#xff08;2&#xff09;创建HPA对象&#xff08;3&#xff09;执行压测 前言…

278 基于Matlab GUI的中重频PD雷达仿真系统

基于Matlab GUI的中重频PD雷达仿真系统。具有26页文档报告。仿真雷达信号的发射、传播、散射、接收、滤波、信号处理、数据处理的全部物理过程&#xff0c;因此应当实现对雷达发射机、天线、接收机、回波信号处理、数据处理的建模与仿真。程序已调通&#xff0c;可直接运行。 2…

自定义类型:结构体+结构体内存对齐+结构体实现位段

结构体内存对齐实现位段 一.结构体1.结构体的声明2.结构体变量成员访问操作符3.结构体传参4.匿名结构体5.结构的自引用 二.结构体内存对齐1.对齐规则2.为什么存在内存对齐&#xff1f;3.修改默认对齐数 三.结构体实现位段1.什么是位段2.位段的内存分配3.位段的跨平台问题4.位段…

使用Qt实现文本文件的读写操作

文章目录 文本读写简介QFileDialog简介常用方法示例代码 QFile简介常用方法示例代码 QTextStream简介常用方法示例代码 结合使用示例完整示例代码(读写操作&#xff0c;可直接复制运行我使用的Qt版本为QT5.14)mainwindow.hmainwindow.cppmain.cpp代码解释 文本读写简介 在现代…

编译原理-词法分析(实验 C语言)

编译原理-词法分析 1. 实验目的 设计、编写并调试一个词法分析程序&#xff0c;加深对词法分析原理的理解 2. 实验要求 2.1 待分析的简单语言的词法 关键字&#xff1a;begin&#xff0c;if&#xff0c;then&#xff0c;while&#xff0c;do&#xff0c;end 所有关键字都是…

uc_os操作练习

目录 一、CubeMX配置 二、获取uc-os源码 三、代码移植 四、代码修改 五、总结 六、参考资料 一、CubeMX配置 首先进入CubeMX&#xff0c;&#xff0c;新建工程&#xff0c;选择STM32F103C8T6芯片&#xff0c;照例配置好RCC和SYS。 然后配置GPIO输出&#xff0c;这里选择P…