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

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

包扫描的过程

测试代码:

// 扫描指定包下的所有类
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()为trueif (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);// 获取根目录资源的URLURL rootDirUrl = rootDirResource.getURL();// 如果存在equinoxResolveMethod且根目录URL的协议以"bundle"开头if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {// 调用equinoxResolveMethod方法,获取解析后的URLURL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);// 如果解析后的URL不为空if (resolvedUrl != null) {// 将解析后的URL赋值给rootDirUrlrootDirUrl = resolvedUrl;}// 将解析后的URL封装为ResourcerootDirResource = 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;// 判断字符串长度是否大于0if (StringUtils.hasLength(strVal)) {// 判断beanName是否为空,或者与strVal不相等if (beanName != null && !strVal.equals(beanName)) {// 抛出异常throw new IllegalStateException("Stereotype annotations suggest inconsistent " +"component names: '" + beanName + "' versus '" + strVal + "'");}// 更新beanNamebeanName = 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,其中也遇到过很多次内存溢出,在合理的选择数据类型上,有必要仔细斟酌。

相关文章:

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

包扫描的过程 测试代码&#xff1a; // 扫描指定包下的所有类 BeanDefinitionRegistry registry new SimpleBeanDefinitionRegistry(); // 扫描指定包下的所有类 ClassPathBeanDefinitionScanner scanner new ClassPathBeanDefinitionScanner(registry); scanner.scan(&quo…...

Java 数学计算 - Random类

在Java中&#xff0c;Random类用于生成伪随机数。这个类在java.util包中&#xff0c;你可以使用它来生成整数、浮点数等不同类型的随机数。以下是关于Random类的一些学习笔记和示例。 1. 创建Random对象 首先&#xff0c;你需要创建一个Random对象。默认情况下&#xff0c;如…...

Ubuntu22.04之解决:无法关机和重启问题(二百四十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…...

大学数字媒体艺术设计网页设计试题及答案,分享几个实用搜题和学习工具 #媒体#职场发展

现在读书可不像小时候&#xff0c;以前想要校对试题答案&#xff0c;都得找到对应的纸质版答案查看&#xff0c;而且有的还只有答案&#xff0c;没有解析&#xff0c;无法弄清楚答案的由来。但是现在不一样了&#xff0c;现在我们可以通过搜题软件&#xff0c;寻找试题的答案&a…...

【ArcGIS微课1000例】0119:TIFF与grid格式互相转换

文章目录 一、任务描述二、tiff转grid三、grid转tif四、注意事项一、任务描述 地理栅格数据常用TIFF格式和GRID格式进行存储。TIFF格式的栅格数据常以单文件形式存储,不仅存储有R、G、B三波段的像素值,还保存有地理坐标信息。GRID格式的栅格数据常以多文件的形式进行存储,且…...

B3870 [GESP202309 四级] 变长编码

[GESP202309 四级] 变长编码 题目描述 小明刚刚学习了三种整数编码方式&#xff1a;原码、反码、补码&#xff0c;并了解到计算机存储整数通常使用补码。但他总是觉得&#xff0c;生活中很少用到 2 31 − 1 2^{31}-1 231−1 这么大的数&#xff0c;生活中常用的 0 ∼ 100 0…...

WordPress网站更换域名后如何重新激活elementor

在创建WordPress网站时&#xff0c;我们常常需要更改域名。但是&#xff0c;在更换域名后&#xff0c;你可能会遇到一个问题&#xff1a;WordPress后台中的Elementor插件授权状态会显示为不匹配。这时&#xff0c;就需要重新激活Elementor插件的授权。下面我会详细说明如何操作…...

linux cron 执行url

linux cron 执行url 在Linux中&#xff0c;你可以使用curl或wget来执行URL。如果你想要定期执行这个操作&#xff0c;可以使用cron来设置定时任务。 以下是一个使用curl在cron中执行URL的例子&#xff1a; 打开终端。 输入 crontab -e 命令来编辑你的cron作业。 添加一个新…...

压缩视频在线压缩网站,压缩视频在线压缩工具软件

在数字化时代&#xff0c;视频成为了人们记录和分享生活的重要载体。然而&#xff0c;视频文件一般都非常大&#xff0c;这不仅占据了大量的存储空间&#xff0c;也给视频的传输和分享带来了不便。因此&#xff0c;压缩视频成为了许多人必须掌握的技能。本文将详细介绍如何压缩…...

linux经典例题编程

编写Shell脚本&#xff0c;计算1~100的和 首先vi 1.sh,创建一个名为1.sh的脚本&#xff0c;然后赋予这个脚本权限&#xff0c;使用命令chmod 755 1.sh&#xff0c;然后就可以在脚本中写程序&#xff0c;然后运行。 shell脚本内容 运行结果&#xff1a; 编写Shell脚本&#xf…...

二叉树的实现(初阶数据结构)

1.二叉树的概念及结构 1.1 概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合&#xff1a; 1.或者为空 2.由一个根结点加上两棵别称为左子树和右子树的二叉树组成 从上图可以看出&#xff1a; 1.二叉树不存在度大于2的结点 2.二叉树的子树有左右之分&#xff0c;次序不能…...

C++笔试强训day41

目录 1.棋子翻转 2.宵暗的妖怪 3.过桥 1.棋子翻转 链接https://www.nowcoder.com/practice/a8c89dc768c84ec29cbf9ca065e3f6b4?tpId128&tqId33769&ru/exam/oj &#xff08;简单题&#xff09;对题意进行简单模拟即可&#xff1a; class Solution { public:int dx[…...

【JavaScript】内置对象 - 字符串对象 ⑤ ( 判断对象中是否有某个属性 | 统计字符串中每个字符出现的次数 )

文章目录 一、判断对象中是否有某个属性1、获取对象属性2、判定对象是否有某个属性 二、统计字符串中每个字符出现的次数1、算法分析2、代码示例 String 字符串对象参考文档 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String 一、判…...

Linux环境下测试服务器的DDR5内存性能

要在Linux环境下测试服务器的DDR5内存性能&#xff0c;可以采用以下几种方法和工具&#xff1a; ### 测试原理 内存性能测试主要关注以下几个关键指标&#xff1a; - **带宽**&#xff1a;内存每秒能传输的数据量。 - **延迟**&#xff1a;内存访问请求从发出到完成所需的时间…...

19、matlab信号预处理中的中值滤波(medfilt1()函数)和萨维茨基-戈雷滤波滤(sgolayfilt()函数)

1、中值滤波&#xff1a;medfilt1()函数 说明&#xff1a;一维中值滤波 1&#xff09;语法 语法1&#xff1a;y medfilt1(x) 将输入向量x应用3阶一维中值滤波器。 语法2&#xff1a;y medfilt1(x,n) 将一个n阶一维中值滤波器应用于x。 语法3&#xff1a;y medfilt1(x,n…...

Scala 练习一 将Mysql表数据导入HBase

Scala 练习一 将Mysql表数据导入HBase 续第一篇&#xff1a;Java代码将Mysql表数据导入HBase表 源码仓库地址&#xff1a;https://gitee.com/leaf-domain/data-to-hbase 一、整体介绍二、依赖三、测试结果四、源码 一、整体介绍 HBase特质 连接HBase, 创建HBase执行对象 初始化…...

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

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

Linux☞进程控制

在终端执行命令时&#xff0c;Linux会建立进程&#xff0c;程序执行完&#xff0c;进程会被终止&#xff1b;Linux是一个多任务的OS,允许多个进程并发运行&#xff1b; Linxu中启动进程的两种途径&#xff1a; ①手动启动(前台进程(命令gedit)...后台进程(命令‘&’)) ②…...

mybatis离谱bug乱转类型

字符串传入的参数被转成了int&#xff1a; Param("online") String online<if test"online 0">and (heart_time is null or heart_time <![CDATA[ < ]]> UNIX_TIMESTAMP(SUBDATE(now(),INTERVAL 8 MINUTE)) )</if><if test"…...

视频监控管理平台LntonCVS视频汇聚平台充电桩视频监控应用方案

随着新能源汽车的广泛使用&#xff0c;公众对充电设施的安全性和可靠性日益重视。为了提高充电桩的安全管理和站点运营效率&#xff0c;LntonCVS公司推出了一套全面的新能源汽车充电桩视频监控与管理解决方案。 该方案通过安装高分辨率摄像头&#xff0c;对充电桩及其周边区域进…...

VS环境Python:深度探索与实用指南

VS环境Python&#xff1a;深度探索与实用指南 在编程领域&#xff0c;VS环境&#xff08;Visual Studio环境&#xff09;与Python的结合&#xff0c;为开发者们提供了一种强大而灵活的开发体验。这种结合不仅提升了开发效率&#xff0c;还增强了代码的可读性和可维护性。然而&…...

SpringBoot整合SpringSecurit(二)通过token进行访问

在文章&#xff1a;SpringBoot整合SpringSecurit&#xff08;一&#xff09;实现ajax的登录、退出、权限校验-CSDN博客 里面&#xff0c;使用的session的方式进行保存用户信息的&#xff0c;这一篇文章就是使用token的方式。 在其上进行的改造&#xff0c;可以先看SpringBoot…...

【算法训练 day50 打家劫舍、打家劫舍Ⅱ、打家劫舍Ⅲ】

目录 一、打家劫舍-LeetCode 198思路 二、打家劫舍Ⅱ-LeetCode 213思路 三.打家劫舍Ⅲ-LeeCode 337思路 一、打家劫舍-LeetCode 198 Leecode链接: leetcode 198 思路 dp数组含义为&#xff1a;当前数组范围下能偷到的最多的钱。递推公式为:dp[j] max(dp[j-2]nums[j],dp[j-1…...

YOLOv8改进 | 卷积模块 | 在主干网络中添加/替换蛇形卷积Dynamic Snake Convolution

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 蛇形动态卷积是一种新型的卷积操作&#xff0c;旨在提高对细长和弯曲的管状结构的特征提取能力。它通过自适应地调整卷积核的权重&#xff0…...

深入解析力扣183题:从不订购的客户(LEFT JOIN与子查询方法详解)

关注微信公众号 数据分析螺丝钉 免费领取价值万元的python/java/商业分析/数据结构与算法学习资料 在本篇文章中&#xff0c;我们将详细解读力扣第183题“从不订购的客户”。通过学习本篇文章&#xff0c;读者将掌握如何使用SQL语句来解决这一问题&#xff0c;并了解相关的复杂…...

牛客NC32 求平方根【简单 二分 Java/Go/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/09fbfb16140b40499951f55113f2166c 思路 Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可*** para…...

王道408数据结构CH3_栈、队列

概述 3.栈、队列和数组 3.1 栈 3.1.1 基本操作 3.1.2 顺序栈 #define Maxsize 50typedef struct{ElemType data[Maxsize];int top; }SqStack;3.1.3 链式栈 typedef struct LinkNode{ElemType data;struct LinkNode *next; }*LiStack;3.2 队列 3.2.1 基本操作 3.2.2 顺序存储…...

Angular 由一个bug说起之六:字体预加载

浏览器在加载一个页面时&#xff0c;会解析网页中的html和css&#xff0c;并开始加载字体文件。字体文件可以通过css中的font-face规则指定&#xff0c;并使用url()函数指定字体文件的路径。 比如下面这样: css font-face {font-family: MyFont;src: url(path/to/font.woff2…...

并查集进阶版

过关代码如下 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> #include<unordered_set> using namespace std;int n, m; vector<int> edg[400005]; int a[400005], be[400005]; // a的作用就是存放要摧毁 int k; int fa[400005]; int daan[400005]…...

贪心(不相交的开区间、区间选点、带前导的拼接最小数问题)

目录 1.简单贪心 2.区间贪心 不相交的开区间 1.如何删除&#xff1f; 2.如何比较大小 区间选点问题 3.拼接最小数 1.简单贪心 比如&#xff1a;给你一堆数&#xff0c;你来构成最大的几位数 2.区间贪心 不相交的开区间 思路&#xff1a; 首先&#xff0c;如果有两个…...

网站验证码 出不来/网络营销软件大全

作者简介&#xff1a;成锁元&#xff0c;Westar实验室技术专家。Westar实验室&#xff08;westar.io&#xff09;&#xff0c;成立于 2018 年&#xff0c;关注于区块链及分布式前沿技术&#xff0c;包括区块链分层架构、二层路由&#xff0c;网络性能、智能合约、PoW 优化等。 …...

湖北商城网站建设/百度竞价网站

https://zhidao.baidu.com/question/173351764.html 转载于:https://www.cnblogs.com/DixinFan/p/9219084.html...

文化建设基金管理有限公司网站/百度下载安装免费

为什么80%的码农都做不了架构师&#xff1f;>>> 用winusb 先安装winusb $ sudo add-apt-repository ppa:colingille/freshlight $ sudo apt-get update $ sudo apt-get install winusb 转载于:https://my.oschina.net/lyyrj/blog/505384...

网络工程师题库/seo实战培训视频

因为市场上有许多的SQL管理工具&#xff0c;所以要为SQL项目管理选择工具是一件有挑战性的事。为大家推荐23款SQL工具的精选列表&#xff0c;希望朋友们喜欢。其中几款已经在昨天的SQL查询优化工具一文中&#xff0c;做过推荐。1.Aqua Data StudioAqua Data Studio是一个功能丰…...

使用wordpress的企业/深圳网站设计专家乐云seo

今天一个小伙伴问我&#xff0c;为什么他新装的vscode在使用感叹号!Tab生成html模板的时候不弹出自动生成模板。 然后我上去就是一通操作&#xff0c;英文状态的感叹号不行&#xff0c;就看网上说ctrlshiftp输入change language mode (更改语言模式),选择HTML,再重新输入发现…...

wordpress发信设置/许昌网站推广公司

1.安装依赖 pip install --upgradetools pip install numpy Matplotlib 2.安装opencv-python(网络一定要通畅) pip install opencv-python...