SpringBoot系列-2 自动装配

背景:

Spring提供了IOC机制,基于此我们可以通过XML或者注解配置,将三方件注册到IOC中。问题是每个三方件都需要经过手动导入依赖、配置属性、注册IOC,比较繁琐。
基于"约定优于配置"原则的自动装配机制为该问题提供了一个解决方案。

不同SpringBoot版本细节部分存在差异,本文基于SpringBoot的2.3.2.RELEASE版本进行说明

1.自动装配机制

SpringBoot在启动时通过SPI机制扫描所有JAR包下的spring.factories文件,将文件中EnableAutoConfiguration包含的配置类全部加载到容器中。
根据各个配置类的条件确定是否进行装载,条件包括:容器中有无指定Bean,类路径中有无指定Class对象等。配置类内部Bean的定义也可通过条件确定是否进行装载。
Spring在spring-boot-autoconfigure包中为三方件定义了很多配置类,并提供了对应的starter依赖;用户只需通过引入对应的starter依赖即可完成对应三方件的组装。

以redis为例:

[1] 在pom.xml中添加redis对应的starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

[2] 在spring配置文件中添加对redis的配置:

spring:
  redis:
    host: localhost
    port: 6379
    timeout: 3000
    database: 0

[3] 测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
public class RedisComponentTest {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Test
    public void testRedis() {
        Assert.assertEquals("testValue", redisTemplate.opsForValue().get("testKey"));
    }
}

Note:在redis中添加"testKey" -> "testValue"后,该测试用例就可以运行成功。
原因分析:
在spring-boot-autoconfigure的spring.factories文件中有如下定义:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
...

进入RedisAutoConfiguration配置类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
 @Bean
 @ConditionalOnMissingBean(name = "redisTemplate")
 public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {//...}

 @Bean
 @ConditionalOnMissingBean
 public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {//...}
}

RedisAutoConfiguration自动配置类的装配条件是@ConditionalOnClass(RedisOperations.class), 即类路径中包含RedisOperations.class. RedisOperations定义在spring-data-redis包中,而依赖的spring-boot-starter-data-redis包含了对spring-data-redis的依赖。

另外,在[SpringBoot系列-1 启动流程]中的也提到过使用jetty代替tomcat的方式:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-tomcat</artifactId>
       </exclusion>
   </exclusions>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jetty</artifactId>
   <version>2.6.4</version>
</dependency>

即类路径中删除了对Tomcat的默认依赖,添加了对Jetty的依赖;在自动配置类 ServletWebServerFactoryConfiguration中因找不到Tomcat.class对象而不会装配Tomcat相关组件,因引入了jetty的starter而装配Jetty容器。

2.自定义starter

除了SpringBoot自定义的starter外,也有第三方自定义的starter, 如常见的mybatis:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

用户也可基于SpringBoot提供的自动装配机制自定义starter,从而可以从多个项目中抽出重复的逻辑,以减少不必要的重复操作。本章通过一个完整的案例进行说明。

2.1 准备pom文件:

<groupId>com.demo</groupId>
// [标注1]
<artifactId>demo-spring-boot-starter</artifactId>
<version>1.0.0</version>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <version>2.7.5</version>
    </dependency>
</dependencies>

Note 1: springboot官方的starter依赖基本是pom, 用于关联需要的依赖项。而用户或者第三方自定义时,starter需要包含:配置类、依赖项、spring.factories文件。另外,命名时需要遵循命名规范:springboot定义的形式如spring-boot-starter-xxx, 用户自定义的形式如xxx-spring-boot-starter.

只需引入spring-boot-autoconfigure依赖即可,因为spring-boot-autoconfigure依赖了spring-boot,而spring-boot依赖了spring.

2.2 定义属性配置类

属性配置类用于提供用户自定义能力:

@ConfigurationProperties("demo.configure")
public class DemoProperties {
    private String userName;

    private String password;
    
    // getter和setter方法
}

用户可以在spring.yml等配置文件中通过"demo.configure"对DemoProperties的userName和password属性进行配置。

2.3 定义服务类

服务类包含了该组件的核心逻辑:

public class DemoService {
    private final DemoProperties demoProperties;

    public DemoService(DemoProperties demoProperties) {
        this.demoProperties = demoProperties;
    }

    public Boolean check(String name, String password) {
        if (name == null || password == null) {
            return false;
        }
        return name.equals(demoProperties.getUserName()) && password.equals(demoProperties.getPassword());
    }
}

此时提供了一个服务方法,校验用户名和密码。

2.4 自动装配类

@Configuration
//导入属性配置类
@EnableConfigurationProperties(DemoProperties.class)
@ConditionalOnClass(DemoService.class)
public class DemoAutoConfiguration {
    @Bean
    public DemoService demoService(DemoProperties demoProperties) {
        return new DemoService(demoProperties);
    }
}

添加了@ConditionalOnClass(DemoService.class)表示当DemoService.class在类路径中时,该自动装配类才会生效。

2.5 spring.factories文件

在resources目录下新增META-INF/spring.factories文件,指定自动配置类:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.caltta.demo.DemoAutoConfiguration

2.6 使用方式

将上述的starter项目install到仓库后,在其他项目中可通过如下方式引入:

<dependency>
    <groupId>com.caltta</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

在application.yml文件中配置:

demo:
  configure:
    userName: root
    password: Root.123

测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
public class DemoComponentTest {
    @Autowired
    private DemoService demoService;

    @Test
    public void testDemoService() {
        Assert.assertTrue(demoService.check("root", "Root.123"));
    }
}

测试用例可正常运行。

3.原理

3.1 @SpringBootApplication注解

@SpringBootApplication注解是由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan注解组成的复合注解:
(1) @SpringBootConfiguration本质上是一个@Configuration注解;
(2) @ComponentScan定义了包扫描路径;
(3) @EnableAutoConfiguration开启自动装配。

@SpringBootApplication注解中定义了几个属性:
(1) scanBasePackages/scanBasePackageClasses桥接给@ComponentScan,用于确定扫描包路径,默认我注解类所在路径;
(2) exclude/excludeName桥接给@EnableAutoConfiguration,用于排除自动装配的类;
(3) proxyBeanMethods桥接给@Configuration注解,用于确定代理类型。

3.2 @EnableAutoConfiguration注解

@EnableAutoConfiguration由@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)组成:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
 Class<?>[] exclude() default {};

 String[] excludeName() default {};
}

其中,@AutoConfigurationPackage注解用于向IOC添加一个BasePackages类型的Bean对象,属性默认为注解所在类的包名。
@Import(AutoConfigurationImportSelector.class)用于向容器导入AutoConfigurationImportSelector对象,该部分是整个装配机制的关键。

3.3 AutoConfigurationImportSelector

AutoConfigurationImportSelector是DeferredImportSelector接口的实现类,更是ImportSelector接口的实现类。
selectImports方法如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	// 判断是否开启自动装配
	if (!isEnabled(annotationMetadata)) {
		return {};
	}
	// 获取&&返回需要装配的类型列表
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

Note: 在ConfigurationClassPostProcessor处理@Import注解时,对于DeferredImportSelector类型调用的是getAutoConfigurationEntry方法。

上述逻辑住就要包含两个方法:
isEnabled方法表示是否开启自动装配,逻辑如下:

protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
        return getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true);
    }
    return true;
}

Note:以"spring.boot.enableautoconfiguration"为key从环境变量中取值,如果为false则关闭自动装配,其他情况(空或者false)开启。如:在application.yml中配置“spring.boot.enableautoconfiguration”值为false即可关闭。
getAutoConfigurationEntry方法用于获取待装配的类:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    
    // 获取注解属性,即@EnableAutoConfiguration的exclude和excludeName
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    
    // 根据SPI机制从spring.factories中加载EnableAutoConfiguration的值
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    
    // 去重,因为spring.factories文件加载自多个jar包-可能有重复
    configurations = removeDuplicates(configurations);
    
    // 根据@EnableAutoConfiguration的exclude和excludeName进行排除
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    
    // 对初步排除的结果进行再次过滤
    configurations = getConfigurationClassFilter().filter(configurations);
    
    // 发送事件&&返回结果
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

getExclusions方法获取需要排除的装配类:

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    // 从环境变量中"spring.autoconfigure.exclude"指定的类型数组
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

Note: 排除自动装配可通过@EnableAutoConfiguration的exclude和excludeName属性,也可通过在application.yml中设置"spring.autoconfigure.exclude"值来进行排除。

getConfigurationClassFilter().filter(configurations)方法对候选的自动装配类进行再一次过滤。
getConfigurationClassFilter()获取配置自动配置过滤器的主要逻辑如下:

private ConfigurationClassFilter getConfigurationClassFilter() {
 //...
    List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();

    this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
     //...
}


protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

Note-1: 获取过滤器:
从spring.factories文件中获取AutoConfigurationImportFilter对应的值。spring-boot-autoconfigure包中的spring.factories文件中有如下定义:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

默认情况下(无用户自定义&&三方件引入),只有OnBeanCondition、OnClassCondition、OnWebApplicationCondition三个过滤器。该过滤器与自动装配的元数据配合实现快速排除不必要的自动配置类,加快容器启动速度。

Note-2: 构造ConfigurationClassFilter

new ConfigurationClassFilter(this.beanClassLoader, filters)方法构造时,传入了过滤器,同时从类路径加载了元数据:

ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
    // 加载"META-INF/spring-autoconfigure-metadata.properties"文件内容
    this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
    this.filters = filters;
}

Note-3: 执行过滤

List<String> filter(List<String> configurations) {
	String[] candidates = StringUtils.toStringArray(configurations);
	boolean skipped = false;
	for (AutoConfigurationImportFilter filter : this.filters) {
		boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
		for (int i = 0; i < match.length; i++) {
			if (!match[i]) {
				candidates[i] = null;
				skipped = true;
			}
		}
	}
	if (!skipped) {
		return configurations;
	}
	List<String> result = new ArrayList<>(candidates.length);
	for (String candidate : candidates) {
		if (candidate != null) {
			result.add(candidate);
		}
	}
	return result;
}

逻辑较为清晰:对每个候选的自动配置类都进行三个过滤器的过滤操作(调用过滤器的match方法),只有三个过滤器都返回true才会保留;否则会被标记为false,然后排除。skipped用于优化流程,没有匹配失败情况,可快速退出。

遍历过滤器调用filter.match(candidates, this.autoConfigurationMetadata)方法,以OnClassCondition为例进行说明。

public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
    // 省略日志...
    ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
    boolean[] match = new boolean[outcomes.length];
    for (int i = 0; i < outcomes.length; i++) {
        match[i] = (outcomes[i] == null || outcomes[i].isMatch());
    }
    return match;
}

入参中: autoConfigurationClasses表示候选的自动装配类列表,autoConfigurationMetadata表示加载的自动配置元数据。
getOutcomes方法根据autoConfigurationMetadata对每个候选的自动装配类生成一个匹配结果,结果为空或者true表示匹配,继续看getOutcomes方法实现细节:

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
    if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
        return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
    } else {
        OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0, autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
        return outcomesResolver.resolveOutcomes();
    }
}

根据处理器个数进行优化,确定是否折成两半分别进行,本质还是调用了StandardOutcomesResolver的resolveOutcomes方法:

public ConditionOutcome[] resolveOutcomes() {
    return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
    ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
    for (int i = start; i < end; i++) {
        String autoConfigurationClass = autoConfigurationClasses[i];
        if (autoConfigurationClass != null) {
            // 从元数据中获取ConditionalOnClass为key尾缀的值
            String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
            if (candidates != null) {
                outcomes[i - start] = getOutcome(candidates);
            }
        }
    }
    return outcomes;
}

Note:
在spring-boot-autoconfigure包中定义的spring-autoconfigure-metadata.properties文件有如下定义:

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=\
org.springframework.data.redis.core.RedisOperations

表示此阶段会根据类路径中是否存在RedisOperations类确定是否排除自动配置类RedisAutoConfiguration。
继续跟踪getOutcome(candidates)方法进入:

private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
    // 会根据类加载机制是否排除异常,确定类是否存在
    if (ClassNameFilter.MISSING.matches(className, classLoader)) {
        return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class").items(Style.QUOTE, className));
    }
    return null;
}

ClassNameFilter.MISSING的matches方法实现如下:

public boolean matches(String className, ClassLoader classLoader) {
    return !isPresent(className, classLoader);
}
static boolean isPresent(String className, ClassLoader classLoader) {
    if (classLoader == null) {
        classLoader = ClassUtils.getDefaultClassLoader();
    }
    try {
        // 使用类加载器加载className
        resolve(className, classLoader);
        return true;
    } catch (Throwable ex) {
        return false;
    }
}

isPresent方法通过类加载器去类路径中加载,加载成功则返回true,否则返回false.
上述为OnClassCondition过滤机制。

4.整体流程

对于一个SpringBoot项目,已经知道了自动装配机制的实现原理;现在再结合@Configuration注解分章节梳理一下Bean的注入IOC的流程。
这部分需要读者对Spring启动流程ConfigurationClassPostProcessorSpringBoot启动流程有比较清晰的理解,可参考:Spring系列-11 @Configuration注解原理 和 SpringBoot系列-1启动流程和 Spring系列-1 启动流程.

4.1 主配置类注入阶段

为表述方便,使用SpringBoot系列-1启动流程中案例进行介绍,如下所示:

@SpringBootApplication
// 标注[1]
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

SpringBoot系列-1启动流程的章节-2.2中介绍:在SpringApplication对象的run方法中,刷新Spring容器前的准备阶段中通过BeanDefinitionLoader将主配置类导入IOC中,即此时主配置类DemoApplication已被导入IOC容器。

4.2 获取配置类

配置类值被@Configuration注解的Bean。ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor接口的实现类,更是BeanFactoryPostProcessor接口实现类,因此在容器刷新阶段会通过invokeBeanFactoryPostProcessors方法调用其勾子方法(时机比注入非懒加载靠前)。
调用勾子逻辑进入ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	// ⚠️第一阶段:
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
		} else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

	// Return immediately if no @Configuration classes were found
	if (configCandidates.isEmpty()) {
		return;
	}

	// Sort by previously determined @Order value, if applicable
	configCandidates.sort((bd1, bd2) -> {
		int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
		int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
		return Integer.compare(i1, i2);
	});

	// ...

	// 	⚠️第二阶段...
}

processConfigBeanDefinitions方法可以分为两个阶段:
(1) 第一阶段:从IOC容器中获取配置类;
(2) 第二阶段:解析配置类获取Bean对象,并讲所有的Bean对象注入到IOC中.

实际上,此时获取的configCandidates获取的就是 章节-4.1 主配置类注入阶段 中注入IOC的DemoApplication.

4.3 解析配置类&&启动自动装配

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	// ⚠️第一阶段...

	// ⚠️第二阶段:
	ConfigurationClassParser 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);
		parser.validate();
		// ...
		Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
		configClasses.removeAll(alreadyParsed);
		this.reader.loadBeanDefinitions(configClasses);

		// ...
	}
	while (!candidates.isEmpty());
	// ...
}

该阶段可以分为两个步骤:解析出所有的Bean、注册解析得到的Bean。核心逻辑在于前者,配置类解析依赖于解析器ConfigurationClassParser,存在递归逻辑,用图解表示如下:
在这里插入图片描述
DemoApplication类上注解了@SpringBootApplication,继而间接注解了@Import(AutoConfigurationImportSelector.class),在4.3 解析配置类阶段会通过ImportSelect逻辑导入AutoConfigurationImportSelector类,从而启动自动装配过程。

Note: 上图中的条件过滤用于处理注解在自动配置类中添加的@Conditional注解。

4.4 条件注解

条件注解的解析和判断在ConditionEvaluator类的shouldSkip中方法进行,读者可自行阅读。

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

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

相关文章

recycleView(二)Grid,中间有间距,left,right,top,bottom没有间距

1.作用 1.效果图 item的top&#xff0c;bottom&#xff0c;right&#xff0c;left都是0 2.代码 1.关键代码 // 设置RecycleView的item间的间距&#xff0c;上下间距为20排序&#xff0c;左右间距为20排序binding.rv.addItemDecoration(object : RecyclerView.ItemDecorat…

算法——滑动窗口

什么是窗口&#xff1f;就是符合题目要求的区域内的数据&#xff0c;将每次符合数据的窗口内的数据记录下来&#xff0c;然后将窗口后移&#xff0c;寻找其他符合要求的数据&#xff0c;每次进入窗口和退出窗口都需要一定的要求 一、 LCR 008. 长度最小的子数组 - 力扣&#…

数据结构—二叉树的模拟实现(c语言)

目录 一.前言 二.模拟实现链式结构的二叉树 2.1二叉树的底层结构 2.2通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 2.3二叉树的销毁 2.4二叉树查找值为x的节点 2.5二叉树节点个数 2.6二叉树叶子节点个数 2.7二叉树第k层节点个数 三.二叉树的遍历 3.1…

Keras实现图注意力模型GAT

简介&#xff1a;本文实现了一个GAT图注意力机制的网络层&#xff0c;可以在Keras中像调用Dense网络层、Input网络层一样直接搭积木进行网络组合。 一&#xff0c;基本展示 如下图所示&#xff0c;我们输入邻接矩阵和节点特征矩阵之后&#xff0c;可以直接调用myGraphAttention…

C语言之pthread_once实例总结(八十三)

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

史上最全最新Ubuntu20.04安装教程(图文)

总的来说&#xff0c;安装Ubantu包含以下三个步骤&#xff1a; 一、安装虚拟机 二、Ubuntu镜像下载 三、虚拟机配置 一、安装虚拟机 选择安装VMware Workstation&#xff0c;登录其官网下载安装包&#xff0c;链接如下&#xff1a; 下载 VMware Workstation Pro​www.vmwa…

设计模式之--原型模式(深浅拷贝)

原型模式 缘起 某天&#xff0c;小明的Leader找到小明:“小明啊&#xff0c;如果有个发简历的需求&#xff0c;就是有个简历的模板&#xff0c;然后打印很多份&#xff0c;要去一份一份展示出来&#xff0c;用编程怎么实现呢&#xff1f;” 小明一听&#xff0c;脑袋里就有了…

ARM64 linux并发与同步之内存屏障

1.2 内存屏障 1.2.1 概念理解 原理部分比较苦涩难懂&#xff0c;我们先不过多详细介绍这部分的由来和经过&#xff0c;接下来着重讲解什么用途和实现&#xff1b; ARM64架构中提供了3条内存屏障指令。 数据存储屏障(Data Memory Barrier, DMB)指令。数据同步屏障(Data Synch…

劲松HPV防治诊疗中心发布:HPV感染全面防治解决方案

在当今社会&#xff0c;HPV(人乳头瘤病毒)感染问题已成为广大公众关注的焦点。作为一种高度传染性的病毒&#xff0c;HPV感染不仅可能导致生殖器疣&#xff0c;还可能引发各种癌症。面对这一严重威胁&#xff0c;劲松HPV防治诊疗中心以其专业的医疗团队、正规的治疗流程和全方位…

Python基础入门例程51-NP51 列表的最大与最小(循环语句)

最近的博文&#xff1a; Python基础入门例程50-NP50 程序员节&#xff08;循环语句&#xff09;-CSDN博客 Python基础入门例程49-NP49 字符列表的长度-CSDN博客 Python基础入门例程48-NP48 验证登录名与密码&#xff08;条件语句&#xff09;-CSDN博客 目录 最近的博文&…

函数极限求解方法归纳

1、连续函数直接代入值&#xff08;加减不可以部分代入值&#xff09; 例题1 配凑构造等价无穷小 等价无穷小 注意&#xff1a;不要在加减中部分使用等价无穷小&#xff0c;可以利用拆极限的方式求&#xff0c;拆出来的每一部分都要有极限&#xff0c;如果有一部分没有极限就是…

STM32F4X定时器之通用定时器

一、STM32通用定时器概述 通用定时器包括一个16位或32位自动重载计数器&#xff0c;可通过可编程预分频器进行驱动。定时器可以实现多种功能&#xff0c;包括测量输入信号的脉冲宽度和生成输出波形&#xff0c;通过使用定时器预分频器和RCC时钟控制器预分频器&#xff0c;可以…

目标检测——Yolo系列(YOLOv1/2/v3/4/5/x/6/7/8)

目标检测概述 什么是目标检测&#xff1f; 滑动窗口&#xff08;Sliding Window&#xff09; 滑动窗口的效率问题和改进 滑动窗口的效率问题&#xff1a;计算成本很大 改进思路 1&#xff1a;使用启发式算法替换暴力遍历 例如 R-CNN&#xff0c;Fast R-CNN 中使用 Selectiv…

C++算法:完美矩形

题目 给你一个数组 rectangles &#xff0c;其中 rectangles[i] [xi, yi, ai, bi] 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 (xi, yi) &#xff0c;右上顶点是 (ai, bi) 。 如果所有矩形一起精确覆盖了某个矩形区域&#xff0c;则返回 true &#xff1b;否则&#xf…

AI 绘画 | Stable Diffusion WebUI的基本设置和插件扩展

前言 Stable Diffusion WebUI是一个基于Gradio库的浏览器界面&#xff0c;用于配置和生成AI绘画作品&#xff0c;并且进行各种精细地配置。它支持目前主流的开源AI绘画模型&#xff0c;例如NovelAI/Stable Diffusion。 在基本设置方面&#xff0c;Stable Diffusion WebUI的默…

asp.net外卖网站系统VS开发mysql数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net外卖网站系统 是一套完善的web设计管理系统&#xff0c;系统采用mvc模式&#xff08;BLLDALENTITY&#xff09;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为mysql&#xff0c;使用c#语…

Git的原理与使用(一)

目录 Git初始 Git安装 Git基本操作 创建git本地仓库 配置git 工作区,暂存区,版本库 添加文件,提交文件 查看.git文件 修改文件 版本回退 小结 Git初始 git是一个非常强大的版本控制工具.可以快速的将我们的文档和代码等进行版本管理. 下面这个实例看理解下为什么需…

CountDownLatch和CyclicBarrier详解

1. CountDownLatch 1.1 简介 CountDownLatch 是 Java 中并发包&#xff08;java.util.concurrent&#xff09;提供的一种同步工具&#xff0c;用于在多线程环境中协调多个线程之间的执行顺序。它的作用是允许一个或多个线程等待其他线程完成操作。 CountDownLatch 通过一个计…

java使用geotools导出shp文件

SHP格式是一种矢量数据格式&#xff0c;用于存储地理信息系统&#xff08;GIS&#xff09;数据。 SHP文件由一系列有序的文件组成&#xff0c;我们导出的shp文件包括.shp、.shx、.dbf、.prj以及.fix文件。 .shp&#xff08;shape&#xff09;文件&#xff1a;存储矢量地图数据&…

自定义类型:联合和枚举

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 1. 联合体 1.1 联合体类型的声明 1.2 联合体的特点 1.3 相同成员的结构体和联合体对比 1.4 联合体大小的计算 1.5 联合的一个练习 2. 枚举类型 2.1 枚举类型的声明…