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

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: localhostport: 6379timeout: 3000database: 0

[3] 测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
public class RedisComponentTest {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Testpublic 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@ConditionalOnMissingBeanpublic 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 {@Beanpublic 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: rootpassword: Root.123

测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
public class DemoComponentTest {@Autowiredprivate DemoService demoService;@Testpublic 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和excludeNameAnnotationAttributes 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 {// 使用类加载器加载classNameresolve(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 foundif (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicableconfigCandidates.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中方法进行,读者可自行阅读。

相关文章:

SpringBoot系列-2 自动装配

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

vue3+ts 前端实现打印功能

1.安装插件 npm install vue3-print-nb --save 2.全局引用 import { createApp } from ‘vue’ import App from ‘./App.vue’ import print from ‘vue3-print-nb’ const app createApp(App) app.use(print) app.mount(‘#app’) 例子 <template><div><el-…...

egg.js sequelize数据库操作配置

egg.js sequelize数据库操作配置 文章目录 egg.js sequelize数据库操作配置1. 数据库配置2. 迁移配置3.数据表设计和迁移4.模型创建 1. 数据库配置 安装并配置egg-sequelize插件&#xff08;它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上&#xff09;和mysql2模块&a…...

vagrant安装k8s集群

目录 概述前期准备安装virtualbox安装vagrant安装gitbash 集群架构集群安装集群初始化集群测试 概述 使用vagrant、virtualbox创建。 前期准备 安装virtualbox 访问官网安装&#xff0c;版本7.0.10 安装vagrant 访问官网安装&#xff0c;版本2.3.7 安装gitbash 访问官网…...

ArcGIS进阶:水源涵养功能分级评价操作

首先抛出水源涵养重要性评价的公式&#xff1a;水源涵养量降雨量-蒸散发量-地表径流量&#xff0c;其中地表径流量降雨量*平均地表径流系数 声明&#xff1a;以下数据来源于来自于牛强老师书籍&#xff08;城乡规划GIS技术&#xff09;。 以下给出重要性评价阈值表&#xff1…...

数据结构与算法 | 第四章:字符串

本文参考网课为 数据结构与算法 1 第四章字符串&#xff0c;主讲人 张铭 、王腾蛟 、赵海燕 、宋国杰 、邹磊 、黄群。 本文使用IDE为 Clion&#xff0c;开发环境 C14。 更新&#xff1a;2023 / 11 / 12 数据结构与算法 | 第四章&#xff1a;字符串 字符串概念字符串字符字符…...

2023-11-rust-struct

struct 类似 schema。 ts的interface 和type struct MyStruct {width: i32,height: i32, } 创建实例 let eg1 MyStruct {width: 23,height: 22,}; struct 可以有自己的方法&#xff0c;并且默认第一个参数是该实例 impl MyStruct {fn can_hold(&self, instance: &…...

Docker容器编排

文章目录 基本概念Docker ComposeSwarm分布式NodeTaskservice集群搭建弹性伸缩 基本概念 针对容器生命周期的管理&#xff0c;对容器生命周期进行更方便更快捷的方式进行管理。 依赖管理&#xff1a;当一个容器必须在另一个容器运行完成后&#xff0c;才能运行时&#xff0c;…...

计算机中丢失mfc140u.dll怎么解决

mfc140u.dll是一个Microsoft Visual C库文件&#xff0c;主要用于MFC&#xff08;Microsoft Foundation Class&#xff09;应用程序的开发。它包含了MFC应用程序所需的一些常用功能&#xff0c;如对话框、窗口、菜单等。当mfc140u.dll丢失时&#xff0c;可能会导致MFC应用程序无…...

postman设置动态token, 每次登录更新token

postman设置动态token, 每次登录更新token 文章目录 postman设置动态token, 每次登录更新token问题1. 设置全局变量2. 新建登录接口3. 设置脚本4. 切换环境5. 配置动态token 问题 token过期时间一般比较短, 每次使用postman调用接口都token非常麻烦 实现token过期后, 调用一次…...

架构师范文(AI写作)两篇

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 架构师范文-论区块链技术及应用 2022年3月&#xff0c;我参与了某集团内部一款基于区块链技术的数字资产管理平台&#xff0c;该平台是为了方便管理公司旗下的各种…...

基于SSM的电子病历系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…...

一次sougo workflow库的使用过程

安装就是常规的make install tutorial http_echoserver实现一下&#xff0c;在macos上实现 cmakelist.txt cmake_minimum_required(VERSION 3.6)set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Release")project(mainLANGUAGES C CXX )set(CMAKE_RUNTIME_OUTP…...

macOS Big Sur(macos11版本)

macOS Big Sur是苹果推出的最新操作系统&#xff0c;具有以下特点&#xff1a; 全新的设计风格&#xff1a;Big Sur采用了全新的设计语言&#xff0c;包括更加圆润的窗口和控件、更加鲜明的色彩和更加简洁的界面。这种设计风格使得操作系统更加美观和易用。强大的性能表现&…...

泛微E-Office信息泄露漏洞复现

简介 Weaver E-Office是中国泛微科技&#xff08;Weaver&#xff09;公司的一个协同办公系统。 Weaver E-Office 9.5版本存在安全漏洞。攻击者利用该漏洞可以访问文件或目录。 漏洞编号&#xff1a;CVE-2023-2766 漏洞复现 FOFA语法&#xff1a; app"泛微-EOffice&qu…...

-bash: sudo: command not found的解决方法

在 Linux 系统中&#xff0c;使用 sudo 命令时提示 “command not found”&#xff0c;首先执行以下命令看一下 /etc/sudoers.d 文件是否存在&#xff1a; find /etc/sudoers.d1&#xff09;如果返回 No such file or directory&#xff0c;就说明系统没有安装sudo&#xff0c…...

CMOS介绍

1 二极管 2 CMOS 2.1 栅极、源极、漏极 2.2 内部结构 2.2 导电原理 - 原理&#xff1a;1.通过门级和衬底加一个垂直电场Ev&#xff0c;从而在两口井之间形成反形层2.如果加的电场足够强&#xff0c;反形层就可以把source&#xff08;源极&#xff09;和drain&#xff08;漏极…...

《软件工程与计算》期末考试真题范例及答案

今天分享一套针对《软件工程与计算》这本书的真题案例&#xff0c;有关《软件工程与计算》23章内容的重点知识整理&#xff0c;已经总结在了博客专栏中&#xff0c;有需要的自行阅读&#xff1a; 《软件工程与计算》啃书总结https://blog.csdn.net/jsl123x/category_12468792.…...

springboot高校全流程考勤系统-计算机毕设 附源码 27637

Springboot高校全流程考勤系统 摘 要 本文针对高校考勤等问题&#xff0c;对其进行研究分析&#xff0c;然后开发设计出高校全流程考勤系统以解决问题。高校全流程考勤系统系统主要功能模块包括&#xff1a;考勤签到、课程信息、考勤情况、申请记录列表等&#xff0c;系统功能设…...

大二第四周总结——用原生js封装一个分页器

用原生js封装一个分页器 起因&#xff1a;这次项目还是用原生的js来写的&#xff0c;我负责的是后台&#xff0c;分页是后台最常见的一个功能了&#xff0c;于是干脆封装一下,废话少说&#xff0c;直接上代码 这里是基本的样式 .pagination {display: flex;width: 600px;hei…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...