SpringBoot 自动配置原理

SpringBoot 自动配置原理

注: 本文使用的springboot版本为 2.7.11

1、@SpringBootApplication

字面分析,这个注解是标注一个Spring Boot应用。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}

其中,我们需要着重分析三个注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

我们依次来进行分析

2、@SpringBootConfiguration

我们进入这个注解里面

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {}

我们可以看到 @SpringBootConfiguration 其实是 @Configuration 的派生注解,那么他们的功能就是一样了,标注这个类是一个配置类。只不过@SpringBootConfiguration是springboot的注解,而@Configuration是spring的注解。那么 @Configuration又是个什么呢?

2.1、 @Configuration

我们知道,spring之前的配置方式是通过xml文件,就像这样,将我们需要的对象以 标签的形式注入到Spring容器中。

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 https://www.springframework.org/schema/beans/spring-beans.xsd">
     
     <bean id="student" class="com.x.pojo.Student"></bean>
     
 </beans>

这样需要我们一个一个的写,当类很多的时候,会很麻烦。这时我们就可以用注解的方式去实现。使用注解的时候,要在配置文件中打开注解支持

<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.x"/>
<!--开启注解配置-->
<context:annotation-config/> 

我们在类上添加一个 @Component 注解,就可以将类标记为Spring中的bean了

@Component //组件 
public class User {
    //等价于 <property name="name" value="嘿嘿"/>
    public String name;
    @Value("嘿嘿")
    public void setName(String name) {
        this.name = name;
    }
}

基于 @Component 扩展的注解还有三个: @controller、@service、@repository

1@controller:   controller控制器层(注入服务)
2@service :      service服务层(注入dao)
3@repository :  dao持久层(实现dao访问)
4@component:  标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>

这里不具体讲四者有什么区别,只是希望大家简单的知道 @service 标记的类,说说这个类时service层的实现就够了。

回归正题:

Spring 3.0 起,支持了另外一种方式,基于Java类的配置方式。

@Configuration
public class AppConfig {
    @Bean
    public User user(){
        return new User();
    }
}

@Configuration 就代表这是一个 配置类。 AppConfig 就相当于我们的 applicationContext.xml

@Bean
User getUser(){
     return new User();//返回要注入bean的对象
}
// getUser 相当于 id
// 返回值就相当于 class 属性
<bean id="user" class="com.x.pojo.User"></bean>

这时候我们发现一个问题 @Bean 和 @Component 都可以将对象交给SPring 托管,那二者有啥区别呢

2.2、@Bean 和 @Component 的区别

@Component(和@Service和@Repository)用于自动检测和使用类路径扫描自动配置bean。注释类和bean之间存在隐式的一对一映射(即每个类一个bean)。
@Bean用于显式声明单个bean,而不是让Spring像上面那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean

该怎么理解这段话呢?

首先 我们知道 @Component 是放在类上的,这样就可以将这个类标记为bean,并交给spring托管。我们完完全全就是在 这个类的 “源码”上进行的配置。那如果我们引入的是第三方的类,不知道源码,那我们怎么用@Component注释呢?这个时候就要用 @Bean了。也就是所谓的声明与类定义分离。我们不需要知道类是怎么定义的。我们只需要声明一个这个类的对象?

那可能又有疑问了,既然是声明对象为什么不这样写呢?

@Configuration
public class AppConfig {
    @Bean
    User user;
}

现在我们去看@Bean的源码:这是源码的第一句话

Indicates that a method produces a bean to be managed by the Spring container.

中文意思是:指示方法生成要由 Spring 容器管理的 Bean。这样就应该能明白为啥放在方法上了吧。

关于@SpringBootConfiguration 就先到这里,它就是将这个类标记为了配置类。

3、@EnableAutoConfiguration

这个注解是自动配置的核心!!!也是重点和难点

//@EnableAutoConfiguration 源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {...}

这里面我们需要关注两个注解 @AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})

3.1、@AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {...}

从注解来看,@AutoConfigurationPackage中使用注解@Import导入了AutoConfigurationPackages.Registrar.class到容器中。

首先我们先来了解一下 @Import 这个注解

@Import

@Import注解用来帮助我们把一些需要定义为Bean的类导入到IOC容器里面,@Import可以添加在@SpringBootApplication(启动类)、@Configuration(配置类)、@Component(组件类)对应的类上。

1@Import引入普通的类可以帮助我们把普通的类定义为Bean@SpringBootApplication
// 通过@Import注解把ImportBean添加到IOC容器里面去(跟直接在bean类上加@Component一样)
@Import(ImportBean.class) 
public class MyBatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisApplication.class, args);
    }
}

2@Import还可以引入一个@Configuration修饰的类(引入配置类),从而把让配置类生效(配置类下的所有Bean添加到IOC容器里面去)。在自定义starter的时候经常会用到。这个后面会讲到,先知道就行
注意:如果配置类在标准的SpringBoot包结构下(SpringBootApplication启动类包的根目录下)。是不需要@Import导入配置类的,SpringBoot自动帮做了。上面的情况一般用于@Configuration配置类不在标准的SpringBoot包结构下面。所以一般在自定义starter的时候用到。

现在,我们继续,我们进入 AutoConfigurationPackages.Registrar 这个类中,这个类是AutoConfigurationPackages的内部类

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
}

该类的重点方法为registerBeanDefinitions()

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(
registry, 
(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])
);}

我们来分析一下AutoConfigurationPackages.register()的第二个参数

(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])

大概意思就是 一个PackageImports对象,获得了 所在的包名

我们进入类 PackageImports的源码中分析

PackageImports(AnnotationMetadata metadata) {
    //从提供的 AnnotationMetadata 对象中获取 AutoConfigurationPackage 注解的属性。
    //创建一个 AnnotationAttributes 对象,使用从注解属性中获取的值。
			AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
	//初始化一个名为 packageNames 的 List<String>,并将其填充为从注解属性中获取的基础包名称。		
    List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
	//遍历注解属性中指定的 basePackageClasses,并将它们的包名添加到 packageNames 列表中。		
    for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
				packageNames.add(basePackageClass.getPackage().getName());
			}
    //如果没有指定基础包,就将提供的 metadata 所代表的类的包名添加到 packageNames 列表中。
			if (packageNames.isEmpty()) {
				packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
			}
    //最后,创建一个从收集到的包名构建的不可修改的列表(通过 Collections.unmodifiableList 方法),并将其赋值给类的 packageNames 字段。
			this.packageNames = Collections.unmodifiableList(packageNames);
		}

在这个构造方法中将元数据即启动类AnnotationMetadata metadata经过处理

获取标签注解信息,注解信息里面的 basePackages 和 basePackageClasses是否有数据。
basePackages、 basePackageClasses为注解@AutoConfigurationPackage中的属性。
如果没有数据则获取注解所在的类的名字目录,放到List中
获得packageNames属性也就是启动类所在的包。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们debug一下,发现确实如此

(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])

就是返回了 启动类所在的包名

然后回到这个方法,第一个参数是个注册表,第二个参数是 启动类所在的包

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(
registry, 
(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])
);}

我们进 register()的源码中看一下

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
 if (registry.containsBeanDefinition(BEAN)) {
   BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
   beanDefinition.addBasePackages(packageNames);
  } else {
        registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
  }
}

这个方法的if语句为判断registry这个参数中是否已经注册了AutoConfigurationPackages的类路径所对应的bean(AutoConfigurationPackages)。如若已经被注册,则把上面分析的第二个参数所获取的包(启动类所在的包的名称)添加到这个bean的定义中。如若没有,则注册这个bean并且把包名设置到该bean的定义中。

总结:@AutoConfigurationPackage就是添加该注解的类所在的包作为自动配置包进行管理。他的实现就是依赖于工具类AutoConfigurationPackages中的内部类Registrar对所标注的包进行注册

3.2、@Import({AutoConfigurationImportSelector.class})

中文意思就是自动配置导入选择器,它会导入哪些组件的选择器呢

类中有两个方法:

selectImports()
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
       return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

它调用了另外一个方法:

getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
       return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

我们先debug一下,看看 configurations 这个集合里面都是什么

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

全都是xxxAutoConfig类,但是我们并不是全都要

configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);

还进行了一系列的检查啥的,去重,去掉不要的,被过滤掉的

它又调用了另外一个方法。

getCandidateConfigurations()
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
    //如果是空的,则提示 在META-INF/spring.factories中没有自动配置类
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

这里有句断言: Assert.notEmpty(configurations, “No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.”);

意思是:“在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。“

这个方法中的返回内容是通过类SpringFactoriesLoader中的静态方法loadFactoryNames()进行获取的。那么就继续进入loadFactoryNames()

loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }

    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

在这个方法中classLoaderToUse是一个关键变量,在这个方法中通过所传进来的参数获得,如果为空则直接获取SpringFactoriesLoader的ClassLoader。然后将第二个参数factoryType的类名传入变量factoryTypeName,最后放入loadSpringFactories(),那么我们还需要知道这个方法实现了什么:

loadSpringFactories()
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
            //public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
                //获取META-INF/spring.factories的详细地址
				URL url = urls.nextElement();
                //封装一下
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

我们俩看一下 result里面是啥

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该方法作用是加载所有依赖的路径META-INF/spring.factories文件,通过map结构保存,key为文件中定义的一些标识工厂类,value就是能自动配置的一些工厂实现的类,value用list保存并去重。

------------------------------------------>
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
{
    ...
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
//已经得知loadSpringFactories根据一个类加载器获得了所有的配置文件,所获得的所有配置进行getOrDefault()处理,如果存在与factoryTypeName相对应的List则返回,如果没有则获取一个空List,由loadSpringFactories可知只要classLoader不为空,则可获取所有配置文件。因此可以判断返回了foryType类名所对应的所有配置

-------------------------------------->
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes){
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                         getBeanClassLoader());
    ...
    return configurations;
}
//再来分析参数一getSpringFactoriesLoaderFactoryClass()
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
//获取到了类EnableAutoConfiguration。参数二就是当前类中的全局变量beanClassLoader。
//通过这两个参数可以获得配置文件中key为EnableAutoConfiguration的所有value。因此可以判断当前方法返回的是以List的配置文件中所有的自动配置的类。

---------------------------------->
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) 
{
    ...
    return new AutoConfigurationEntry(configurations, exclusions);
}
//这个方法是将获取的所有自动配置类进行去重、排除、过滤等一系列的操作然后返回处理完成后的配置,并将其包装为AutoConfigurationEntry
------------------------------>
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) 
{
    ...
   	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//最后回到梦开始的地方,这一步是将所有的配置类转成字符串数组,然后通过@Import的使用方法————将数组内的所有配置导入到容器中。

总结:@EnableAutoConfiguration的实现方式是导入配置文件META-INF/spring.factories中EnableAutoConfiguration所对应的所有的类

4、@ComponentScan

@ComponentScan主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中。部分源码如下:我们先部了解其他的东西,先知道它的基本作用

/**
 * 控制符合组件检测条件的类文件   默认是包扫描下的  **/*.class
 * @return
 */
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
 * 是否对带有@Component @Repository @Service @Controller注解的类开启检测,默认是开启的
 * @return
 */
 boolean useDefaultFilters() default true;

有源码可知

SpringBoot默认包扫描机制: 从启动类所在包开始,扫描当前包及其子级包下的所有文件。这也就是为什么我们在创建 package时,要在启动类的同级目录了,只有这样,我们定义的class才能被扫描到(当然,比在统一目录也可以,这是就需要我们手动指定扫描路径了,比较麻烦)。

在一个springboot项目中,我们发现没有applicationContext.xml文件,那我们的类是怎么交给 spring托管呢。就是@SpringBootApplication,它的存在使得我们启动类本身就是一个配置类(相当于配置文件了),同时 @ComponentScan 也会去扫描所有的类,将加有相应注解的类交给spring托管。

5、@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是 所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪 些自动配置类生效;

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3 setup函数

一、setup函数介绍 setup函数是Vue3中全新的一个配置项&#xff0c;值为一个函数&#xff0c;是所有 Composition API 中“表演的舞台”。 我们在Vue2中用到的所有数据、方法&#xff0c;都需要配置在setup中。 这是我们在Vue2中的写法&#xff1a; 这是我们在Vue3 setup中的…

基于斑点鬣狗算法优化概率神经网络PNN的分类预测 - 附代码

基于斑点鬣狗算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于斑点鬣狗算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于斑点鬣狗优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

基于被囊群算法优化概率神经网络PNN的分类预测 - 附代码

基于被囊群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于被囊群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于被囊群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

postman上传照片,视频,音频等上传文件操作测试方法

Postman上传照片&#xff0c;视频&#xff0c;音频等上传文件操作测试方法 新建一个request&#xff0c;更改请求方式&#xff0c;点击Body 勾选form-data ,key后面下拉框选择File 上一步勾选后Value即出现选择本地文件按钮&#xff0c;填写Key&#xff0c;选择文件即可 此时…

nRF5 SDK 入门(三、理解 nRF5 SDK 应用与协议栈分开烧录)

说明一下 Nordic nRF5 SDK 软件 应用程序 和 协议栈分开烧录的理解前言 上一篇文章我们了解了 Nordic nRF5 SDK 目录结构&#xff0c;在那之前我们也已经搭建好了开发环境&#xff0c;实际上我们就已经可以进入我们的开发之旅了&#xff0c;但是如果刚接触 Nordic 蓝牙开发的小…

Linux中字符设备的打开、写入

一个内核模块应该由以下几部分组成。 第一部分&#xff0c;头文件部分。一般的内核模块&#xff0c;都需要 include 下面两个头文件&#xff1a; #include <linux/module.h> #include <linux/init.h> 第二部分&#xff0c;定义一些函数&#xff0c;用于处理内核…

改进YOLO系列 | YOLOv5/v7 引入Super Token Sampling ViT | 《CVPR 2023 最新论文》

论文地址:https://arxiv.org/abs/2211.11167 代码地址:https://github.com/hhb072/STViT 视觉变换器已经在许多视觉任务中取得了令人印象深刻的性能。然而,它在捕捉浅层的局部特征时可能会受到高度冗余的影响。因此,引入了局部自注意力或早期卷积,这些方法牺牲了捕捉长距…

Nacos热更新

Nacos热更新 相比其他注册中心&#xff0c;Nacos的优势之一在于热更新。 热更新&#xff0c;就是不需要重启服务&#xff0c;就能够更新配置。 nacos配置中心 首先&#xff0c;需要搭建 Nacos&#xff0c;详情见&#xff1a; https://www.cnblogs.com/expiator/p/17392549.h…

K8S知识点(十)

&#xff08;1&#xff09;Pod详解-启动命令 创建Pod&#xff0c;里面的两个容器都正常运行 &#xff08;2&#xff09;Pod详解-环境变量 &#xff08;3&#xff09;Pod详解-端口设置 &#xff08;4&#xff09;Pod详解-资源配额 修改&#xff1a;memory 不满足条件是不能正常…

第27章_事务原理之MVCC与锁机制

文章目录 MVCCread view聚集索引的隐藏列事务的可见性问题快照读当前读 redologundolog锁机制锁类型共享锁&#xff08;S&#xff09;排他锁&#xff08;X&#xff09;意向共享锁&#xff08;IS&#xff09;意向排他锁&#xff08;IX&#xff09;锁的兼容性 锁算法锁兼容关于锁…

Linux MMC子系统 - 4.eMMC 5.1常用命令说明(2)

By: Ailson Jack Date: 2023.11.12 个人博客&#xff1a;http://www.only2fire.com/ 本文在我博客的地址是&#xff1a;http://www.only2fire.com/archives/163.html&#xff0c;排版更好&#xff0c;便于学习&#xff0c;也可以去我博客逛逛&#xff0c;兴许有你想要的内容呢。…

小程序中如何设置门店信息

小程序是商家转型升级的利器&#xff0c;小程序中门店信息的准确性和完整性对于用户的体验和信任度都有很大的影响。下面具体介绍门店信息怎么在小程序中进行设置。 在小程序管理员后台->门店设置处&#xff0c;可以门店设置相关。主要分为2个模块&#xff0c;一个是门店级…

AD教程 (十三)常见CHIP封装的创建

AD教程 &#xff08;十三&#xff09;常见CHIP&#xff08;贴片&#xff09;封装的创建 PCB封装是电子设计图纸和实物之间的映射体&#xff0c;具有精准数据的要求&#xff0c;在实际设计中需要通过规格书获取创建封装的数据参数。 PCB封装和实物的大小一致。PCB封装是承载实物…

Linux进程的认识与了解[上]

文章目录 1.何为进程?1.1对进程的认识1.2基本概念 2.OS如何管理大量进程?2.1图解2.2进程的形成 3.何为PCB?3.1对PCB的认识3.2task_ struct内容分类3.2对进程表的认识 4.查看进程4.1基础指令4.2获取某进程的PID(process id)4.3杀死进程4.4获取当前进程的父进程的ppid(parent …

Vant 移动端UI

Vue项目中安装Vant # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant 组件按需引入配置 Vant按需引入- - -安装&#xff1a;unplugin-vue-components 插件 unplugin-vue-components 插件可以在Vue文件中自动引入组件&#xff08;包括项目自身的组件和各种组件库中的组件&…

RK3568平台开发系列讲解(Linux系统篇)Linux 目录结构

🚀返回专栏总目录 文章目录 一、VFS二、分区结构三、挂载 mount四、目录结构沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们从目录管理入手,会更直观的理解 linux 的目录结构。 一、VFS Linux 所有的文件都建立在虚拟文件系统(Virtual File System ,VFS…

事件循环Eventloop

事件循环 浏览器的进程模型 何为进程&#xff1f; 程序运行需要有它自己专属的内存空间&#xff0c;可以把这块内存空间简单的理解为进程 每个应用至少有一个进程&#xff0c;进程之间相互独立&#xff0c;即使要通信&#xff0c;也需要双方同意。 何为线程&#xff1f; 有…

【Devchat 插件】创建一个GUI应用程序,使用Python进行加密和解密

VSCode 插件 DevChat——国内开源的 AI 编程&#xff01; 写在最前面DevChat是什么&#xff1f;什么是以提示为中心的软件开发 &#xff08;PCSD&#xff09;&#xff1f;为什么选择DevChat&#xff1f;功能概述情境构建添加到上下文生成提交消息提示扩展 KOL粉丝专属福利介绍D…

【原创课设】java+swing+mysql选课管理系统设计与实现

摘要&#xff1a; 随着学校规模的扩大和课程设置的多样化&#xff0c;传统的手工选课管理方式已经无法满足现代教育的需求。因此&#xff0c;开发一款高效、便捷的选课管理系统变得尤为重要。该系统可以提高选课工作的效率&#xff0c;减少人为错误&#xff0c;同时也能为学生…

linux循环继续fordodone数值处理和脚本的追踪调试

格式 for &#xff08;&#xff08;初始值&#xff1a;限制值&#xff1b;步长&#xff09;&#xff09; do 程序段 done 注意点&#xff1a;$(()) 数值运算 $()命令 ${}取值 sh [-nvx] *.sh -n 不执行脚本&#xff0c;检查语法错误-常用 -v 执行之前&#xff0c;将…