SpringCache源码解析(三)——@EnableCaching

一、源码阅读

让我们进行源码阅读把。

1.1 阅读源码基础:

@Import(xxx.class)里的类可以有两种类:

  • ImportSelector接口的实现类;
  • ImportBeanDefinitionRegistrar接口的实现类;

两种接口简介:

  • ImportSelector接口:在配置类中被@Import加入到Spring容器中以后。Spring容器就会把ImportSelector接口方法返回的字符串数组中的类new出来对象然后放到工厂中去。

  • ImportBeanDefinitionRegistrar:ImportBeanDefinitionRegistrar接口是也是spring的扩展点之一,它可以支持我们自己写的代码封装成BeanDefinition对象,注册到Spring容器中,功能类似于注解@Service @Component。

Spring框架中ImportBeanDefinitionRegistrar的应用详解

ImportBeanDefinitionRegistrar的作用

1.2 CachingConfigurationSelector

在这里插入图片描述

@EnableCaching导入了CachingConfigurationSelector。
org.springframework.cache.annotation.CachingConfigurationSelector:

@Overridepublic String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:return getProxyImports();case ASPECTJ:return getAspectJImports();default:return null;}}

getProxyImports方法:

private String[] getProxyImports() {List<String> result = new ArrayList<>(3);//将AutoProxyRegistrar注入到容器result.add(AutoProxyRegistrar.class.getName());result.add(ProxyCachingConfiguration.class.getName());if (jsr107Present && jcacheImplPresent) {result.add(PROXY_JCACHE_CONFIGURATION_CLASS);}return StringUtils.toStringArray(result);}

getAspectJImports方法:

private String[] getAspectJImports() {List<String> result = new ArrayList<>(2);result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);if (jsr107Present && jcacheImplPresent) {result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);}return StringUtils.toStringArray(result);}

1.3 getProxyImports方法内部

1.3.1 AutoProxyRegistrar

  顾名思义,AutoProxyRegistrar从名称上就可以看出其本质是一个ImportBeanDefinitionRegistrar,而ImportBeanDefinitionRegistrar最重要的方法为registerBeanDefinitions(),这个方法的主要作用就是用方法参数中的registry向容器中注入一些BeanDefinition。

源码追踪:

@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {boolean candidateFound = false;Set<String> annTypes = importingClassMetadata.getAnnotationTypes();//1.遍历类元数据中的所有注解类型for (String annType : annTypes) {AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);if (candidate == null) {continue;}Object mode = candidate.get("mode");Object proxyTargetClass = candidate.get("proxyTargetClass");//2.检查每个注解是否包含mode和proxyTargetClass属性,并判断其类型是否正确。if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&Boolean.class == proxyTargetClass.getClass()) {candidateFound = true;//3.如果属性类型正确且mode为AdviceMode.PROXY,则根据proxyTargetClass值配置自动代理创建器。if (mode == AdviceMode.PROXY) {AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);if ((Boolean) proxyTargetClass) {AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);return;}}}}if (!candidateFound && logger.isInfoEnabled()) {String name = getClass().getSimpleName();logger.info(String.format("%s was imported but no annotations were found " +"having both 'mode' and 'proxyTargetClass' attributes of type " +"AdviceMode and boolean respectively. This means that auto proxy " +"creator registration and configuration may not have occurred as " +"intended, and components may not be proxied as expected. Check to " +"ensure that %s has been @Import'ed on the same class where these " +"annotations are declared; otherwise remove the import of %s " +"altogether.", name, name, name));}}

该方法用于注册Bean定义。主要功能如下:

  1. 遍历类元数据中的所有注解类型。
  2. 检查每个注解是否包含mode和proxyTargetClass属性,并判断其类型是否正确。
  3. 如果属性类型正确且mode为AdviceMode.PROXY,则根据proxyTargetClass值配置自动代理创建器。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下面这是@EnableCaching里的mode值和proxyTargetClass值:
在这里插入图片描述
下面这个是@Configration注解里的mode值和proxyTargetClass的值,可以看到是null
在这里插入图片描述
友友们,这两个值是从哪来的呢?看这里:
在这里插入图片描述
其实就是注解里定义的呀。那么问题来了,这两个值是干嘛的,什么情况下要定义这两个值?

拿@EnableCaching 里的这两个值打个比方。它提供了几个属性来控制缓存的行为:

  1. mode():此属性决定了 Spring AOP(面向切面编程)使用的模式。它可以取两个值:
      AdviceMode.PROXY:表示使用代理模式实现AOP,这是默认值。
      AdviceMode.ASPECTJ:表示使用 AspectJ 模式实现AOP。
  2. proxyTargetClass():此属性仅在 mode 设置为 AdviceMode.PROXY 时有效。它指定了是否使用 CGLIB 代理(true)而不是 >JDK 动态代理(false,默认)。CGLIB 代理可以代理不可代理的类(即没有实现接口的类),而 JDK 动态代理只能代理实现了接口的类。

这些属性帮助开发者根据应用的需求选择合适的代理机制,从而更灵活地管理缓存逻辑。

拓展:AutoProxyRegistrar里关键代码分析

1.3.1.1 AutoProxyRegistrar.registerBeanDefinitions()方法总结

总结:AutoProxyRegistrar.registerBeanDefinitions()往容器中注入了一个类InfrastructureAdvisorAutoProxyCreator。

这段是不是很熟悉,没错,声明式事务中注入的也是这个InfrastructureAdvisorAutoProxyCreator类,用来生成代理对象。

1.3.2 ProxyCachingConfiguration

ProxyCachingConfiguration向Spring容器中注入了三个Bean:

  1. BeanFactoryCacheOperationSourceAdvisor:切面,用来匹配目标方法和目标类,看那些方法需要实现缓存的增强;
  2. CacheOperationSource:用来解析缓存相关的注解;
  3. CacheInterceptor:实现了Advice,是一个通知,实现对目标方法的增强。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();advisor.setCacheOperationSource(cacheOperationSource());advisor.setAdvice(cacheInterceptor());if (this.enableCaching != null) {advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));}return advisor;}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public CacheOperationSource cacheOperationSource() {return new AnnotationCacheOperationSource();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public CacheInterceptor cacheInterceptor() {CacheInterceptor interceptor = new CacheInterceptor();interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);interceptor.setCacheOperationSource(cacheOperationSource());return interceptor;}}

ProxyCachingConfiguration继承自AbstractCachingConfiguration,AbstractCachingConfiguration是抽象基础配置类,为启用Spring的注解驱动的缓存管理功能提供通用结构;代码如下:
在这里插入图片描述

1.3.2.1 BeanFactoryCacheOperationSourceAdvisor

BeanFactoryCacheOperationSourceAdvisor是一个切面,切面需要包括通知和切点:

  1. Advice通知:CacheInterceptor,由ProxyCachingConfiguration注入;
  2. Pointcut切点:内部CacheOperationSourcePointcut类型的属性;
1.3.2.2 CacheOperationSourcePointcut

Pointcut主要包含两部分,对目标类的匹配,对目标方法的匹配。
对目标类的匹配,其实啥也没干,返回true,主要逻辑在对目标方法的匹配上,大部分Pointcut都是如此:

private class CacheOperationSourceClassFilter implements ClassFilter {@Overridepublic boolean matches(Class<?> clazz) {if (CacheManager.class.isAssignableFrom(clazz)) {return false;}// 啥也没干CacheOperationSource cas = getCacheOperationSource();return (cas == null || cas.isCandidateClass(clazz));}
}

对目标方法的匹配:

public boolean matches(Method method, Class<?> targetClass) {CacheOperationSource cas = getCacheOperationSource();/*** @see AbstractFallbackCacheOperationSource#getCacheOperations(java.lang.reflect.Method, java.lang.Class)*/// 就是看目标方法上面有没有缓存注解return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}

这里会委托给CacheOperationSource来解析缓存注解,这个类是在ProxyCachingConfiguration中注入的。

1.3.3 org.springframework.cache.jcache.config.ProxyJCacheConfiguration

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();advisor.setCacheOperationSource(cacheOperationSource());advisor.setAdvice(cacheInterceptor());if (this.enableCaching != null) {advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));}return advisor;}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public CacheOperationSource cacheOperationSource() {return new AnnotationCacheOperationSource();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public CacheInterceptor cacheInterceptor() {CacheInterceptor interceptor = new CacheInterceptor();interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);interceptor.setCacheOperationSource(cacheOperationSource());return interceptor;}}

ProxyCachingConfiguration向Spring容器中注入了三个Bean:

  1. BeanFactoryCacheOperationSourceAdvisor:切面,用来匹配目标方法和目标类,看那些方法需要实现缓存的增强;
  2. CacheOperationSource:用来解析缓存相关的注解;
  3. CacheInterceptor:实现了Advice,是一个通知,实现对目标方法的增强。
1.3.3.1 BeanFactoryCacheOperationSourceAdvisor

BeanFactoryCacheOperationSourceAdvisor是一个切面,切面需要包括通知和切点:

  • Advice通知:CacheInterceptor,由ProxyCachingConfiguration注入;
  • Pointcut切点:内部CacheOperationSourcePointcut类型的属性;
1.3.3.2 CacheOperationSourcePointcut

Pointcut主要包含两部分,对目标类的匹配,对目标方法的匹配。

对目标类的匹配,其实啥也没干,返回true,主要逻辑在对目标方法的匹配上,大部分Pointcut都是如此:
在这里插入图片描述

abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {@Overridepublic boolean matches(Method method, Class<?> targetClass) {if (CacheManager.class.isAssignableFrom(targetClass)) {return false;}// 就是看目标方法上面有没有缓存注解CacheOperationSource cas = getCacheOperationSource();return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));}
}

这里会委托给CacheOperationSource来解析缓存注解,这个类是在ProxyCachingConfiguration中注入的。

最终会调用到org.springframework.cache.annotation.SpringCacheAnnotationParser#parseCacheAnnotations(org.springframework.cache.annotation.SpringCacheAnnotationParser.DefaultCacheConfig, java.lang.reflect.AnnotatedElement, boolean)

private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {Collection<? extends Annotation> anns = (localOnly ?AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));if (anns.isEmpty()) {return null;}// @Cacheable解析为CacheableOperation// @CacheEvict解析为CacheEvictOperation// @CachePut解析为CachePutOperationfinal Collection<CacheOperation> ops = new ArrayList<>(1);anns.stream().filter(ann -> ann instanceof Cacheable).forEach(ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));anns.stream().filter(ann -> ann instanceof CachePut).forEach(ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));anns.stream().filter(ann -> ann instanceof Caching).forEach(ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));return ops;
}
1.3.3.3 CacheInterceptor

CacheInterceptor是一个Advice,用来实现对目标方法的增强,当调用目标方法时会先进入CacheInterceptor.invoke()方法:

org.springframework.cache.interceptor.CacheInterceptor#invoke

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {@Override@Nullablepublic Object invoke(final MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();CacheOperationInvoker aopAllianceInvoker = () -> {try {// 调用目标方法,后面的代码会回调到这里return invocation.proceed();}catch (Throwable ex) {throw new CacheOperationInvoker.ThrowableWrapper(ex);}};try {return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());}catch (CacheOperationInvoker.ThrowableWrapper th) {throw th.getOriginal();}}}

org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.reflect.Method, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts)

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {// Special handling of synchronized invocationif (contexts.isSynchronized()) { // false不会进入CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);Cache cache = context.getCaches().iterator().next();try {return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));}catch (Cache.ValueRetrievalException ex) {// Directly propagate ThrowableWrapper from the invoker,// or potentially also an IllegalArgumentException etc.ReflectionUtils.rethrowRuntimeException(ex.getCause());}}else {// No caching required, only call the underlying methodreturn invokeOperation(invoker);}}// Process any early evictions// 先调用@CacheEvict注解中的属性beforeInvocation=true的,实际上这个值默认为false,这里一般不会干啥,看后面的调用// cache.evict()processCacheEvicts(contexts.get(CacheEvictOperation.class), true,CacheOperationExpressionEvaluator.NO_RESULT);// Check if we have a cached item matching the conditions// cache.get()Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));// Collect puts from any @Cacheable miss, if no cached item is foundList<CachePutRequest> cachePutRequests = new LinkedList<>();if (cacheHit == null) {collectPutRequests(contexts.get(CacheableOperation.class),CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);}Object cacheValue;Object returnValue;if (cacheHit != null && !hasCachePut(contexts)) {// If there are no put requests, just use the cache hitcacheValue = cacheHit.get();returnValue = wrapCacheValue(method, cacheValue);}else {// Invoke the method if we don't have a cache hit// 调用目标方法returnValue = invokeOperation(invoker);cacheValue = unwrapReturnValue(returnValue);}// Collect any explicit @CachePutscollectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);// Process any collected put requests, either from @CachePut or a @Cacheable missfor (CachePutRequest cachePutRequest : cachePutRequests) {cachePutRequest.apply(cacheValue);}// Process any late evictions// 调用@CacheEvict注解中的属性beforeInvocation=false的processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;
}

1.4 扩展缓存注解

有时候由于业务需要或者Spring提供的缓存不满足我们的要求,如无法解决缓存雪崩问题,扩展步骤如下:

  1. 实现CacheManager接口或继承AbstractCacheManager,管理自身的cache实例,也可以直接使用内置的SimpleCacheManager;
  2. 实现Cache接口,自定义缓存实现逻辑;
  3. 将自定义的Cache和CacheManager进行关联并注入到Spring容器中。

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

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

相关文章

什么是 TDengine?

TDengine 是一款专为物联网、工业互联网等场景设计并优化的大数据平台&#xff0c;其核心模块是高性能、集群开源、云原生、极简的时序数据库。它能安全高效地将大量设备、数据采集器每天产生的高达 TB 甚至 PB 级的数据进行汇聚、存储、分析和分发&#xff0c;对业务运行状态进…

kaggle竞赛平台上数据集下载详解

引言 kaggle作为一个数据分析竞赛平台不仅可以上传代码和数据集&#xff0c;参与一些公开的竞赛&#xff0c;同时也可以下载别人上传的数据集。本文着重介绍如何注册kaggle账号&#xff0c;在本地机上安装kaggle API,以及从kaggle数据集界面上下载想要的数据集到指定位置。 文章…

腾讯面试:说说6大Nginx负载均衡?手写一下权重轮询策略?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 1.讲一下什么是负载均衡&#xff0c;什么是轮询策略、…

Vue/cli不同环境下打包后js文件没有添加hash值-会导致缓存问题-解决

环境变量 包文件判断是根据NODE_ENV=production,这时会对应打包加上hash值,所以在配置不同环境对应命令的时候,把NODE_ENV=production加上 全局的环境变量需要以VUE_APP_ 开头 process.env.VUE_APP_ENV 会读取不到值 .env 文件配置 NODE_ENV=production 才会按照hash模式去…

一、selenium自动化简介selenium工具集

文章目录 一、简介二、组成部分三、selenium工具集3.1 Selenium IDE3.2 Selenium WebDriver3.3 Selenium Grid3.4 Appium 一、简介 官方网站 Selenium 是支持 web 浏览器自动化的一系列工具和库的综合项目。 它提供了扩展来模拟用户与浏览器的交互&#xff0c;用于扩展浏览器分…

如何通过商品id商品链接来获取淘宝商品主图详情图等数据?

在电子商务领域&#xff0c;获取商品信息&#xff0c;尤其是商品的主图、详情图以及其他相关数据&#xff0c;对于商家进行竞品分析、价格监控、商品上架前的信息整合等场景至关重要。淘宝作为中国最大的电子商务平台之一&#xff0c;其商品信息的获取更是众多商家和开发者关注…

Windows环境利用VS2022编译 libvpx 源码教程

libvpx libvpx 是一个开源的视频编码库&#xff0c;由 WebM 项目开发和维护&#xff0c;专门用于 VP8 和 VP9 视频编码格式的编解码处理。它支持高质量的视频压缩&#xff0c;广泛应用于视频会议、在线教育、视频直播服务等多种场景中。libvpx 的特点包括跨平台兼容性、硬件加速…

【Python】数据可视化之核密度

KDEPlot&#xff08;Kernel Density Estimate Plot&#xff0c;核密度估计图&#xff09;是seaborn库中一个用于数据可视化的函数&#xff0c;它基于核密度估计&#xff08;KDE&#xff09;这一非参数统计方法来估计数据的概率密度函数。KDEPlot能够直观地展示数据的分布特征&a…

《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》Chapter 1课件2024

每一轮备课都有新的感悟。 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码

位运算技巧总结

一、常见位运算操作 1、基础位运算 & 按位与 有0则0 | 按位或 有1则1 ^ 按位异或 相同为0 不同为1 2、确定数n的二进制位中第x位是0还是1 目的&#xff1a;是0返回0&#xff0c;是1返回1 (n >> x) & 1 思路&#xff1a;1除了第一位其他位都是0&a…

Docker 部署 Kafka (图文并茂超详细)

部署 Kafka ( Docker ) Kafka对于zookeeper是强依赖&#xff0c;保存kafka相关的节点数据&#xff0c;所以安装Kafka之前必须先安装zookeeper [Step 1] : 部署 Zookeeper -> 拉取 Zookeeper 镜像 ➡️ 启动 Zookeeper 容器 docker pull zookeeper:3.4.14 docker run -d --…

Qt/C++编写的Onvif调试助手调试神器工具/支持云台控制/预置位设置等/有手机版本

一、功能特点 广播搜索设备&#xff0c;支持IPC和NVR&#xff0c;依次返回。可选择不同的网卡IP进行对应网段设备的搜索。依次获取Onvif地址、Media地址、Profile文件、Rtsp地址。可对指定的Profile获取视频流Rtsp地址&#xff0c;比如主码流地址、子码流地址。可对每个设备设…

matlab读取NC文件(含group)

matlab读取NC文件&#xff08;含group&#xff09;&#xff1a; NC文件数据结构&#xff1a; 代码&#xff1a; % 打开 NetCDF 文件 filename your_file.nc; % 替换为你的文件名% 使用 netcdf.open 函数打开文件 ncid netcdf.open(filename, NC_NOWRITE);% 查看文件中的组 …

手把手教你使用亚马逊云服务器创建EC2实例

陈老老老板&#x1f934; &#x1f9d9;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f9d9;‍♂️本文简述&#xff1a;如何使用亚马逊云服务器创建EC2实例。 &#x1f9d9;‍♂…

钢琴灯哪个牌子好?五款学生钢琴灯测评

在这个快节奏的时代&#xff0c;孩子们都面临着长时间用眼的问题&#xff0c;而长时间处于室内不良的光线环境很容易对孩子的视力健康产生影响&#xff0c;对于目前有娃的家庭&#xff0c;很多家长都在给孩子寻找可以提高室内光学环境的钢琴灯&#xff0c;钢琴灯作为一种通过专…

【分支-快速排序】

【分支-快速排序】 1. 颜色分类1.1 题目来源1.2 题目描述1.3 题目解析 2. 排序数组2.1 题目来源2.2 题目描述2.3 题目解析 3. 数组中的第K个最大元素3.1 题目来源3.2 题目描述3.3 题目解析 4. 库存管理 III4.1 题目来源4.2 题目描述4 .3 题目解析 1. 颜色分类 1.1 题目来源 7…

如何使用QT完成记事本程序的UI界面布局

每日QT技巧查询表-CSDN博客 会持续更新记事本编写的全部过程&#xff0c;关注不迷路 一、相关控件 ①水平和垂直布局 ②按键 ③文本框 ④水平弹簧 ⑤标签 ⑥Widget 二、控件使用方法 1、PushButton 拖出三个按键&#xff0c;并对其进行命名&#xff0c;两处地方命名可以不一…

数据结构——线性表(顺序存储结构和单链表结构)

线性表的定义 线性表&#xff08;List&#xff09;&#xff1a;由零个或多个数据元素组成的有限序列。 &#xff08;1&#xff09;它是一个序列&#xff0c;也就是元素之间有个先来后到的&#xff1b; &#xff08;2&#xff09;若元素有多个&#xff0c;则第一个元素无前驱…

[数据集][目标检测]人脸口罩佩戴目标检测数据集VOC+YOLO格式8068张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;8068 标注数量(xml文件个数)&#xff1a;8068 标注数量(txt文件个数)&#xff1a;8068 标注…

Spring Boot实现文件上传和下载

1.背景 项目中经常会有上传和下载的需求&#xff0c;这篇文章简述一下springboot项目中实现简单的上传和下载。 2.代码工程 实验目标 实现简单的文件上传和下载 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://…