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启动流程和ConfigurationClassPostProcessor和SpringBoot启动流程有比较清晰的理解,可参考: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 自动装配
背景: Spring提供了IOC机制,基于此我们可以通过XML或者注解配置,将三方件注册到IOC中。问题是每个三方件都需要经过手动导入依赖、配置属性、注册IOC,比较繁琐。 基于"约定优于配置"原则的自动装配机制为该问题提供了一…...
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插件(它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上)和mysql2模块&a…...
vagrant安装k8s集群
目录 概述前期准备安装virtualbox安装vagrant安装gitbash 集群架构集群安装集群初始化集群测试 概述 使用vagrant、virtualbox创建。 前期准备 安装virtualbox 访问官网安装,版本7.0.10 安装vagrant 访问官网安装,版本2.3.7 安装gitbash 访问官网…...
ArcGIS进阶:水源涵养功能分级评价操作
首先抛出水源涵养重要性评价的公式:水源涵养量降雨量-蒸散发量-地表径流量,其中地表径流量降雨量*平均地表径流系数 声明:以下数据来源于来自于牛强老师书籍(城乡规划GIS技术)。 以下给出重要性评价阈值表࿱…...
数据结构与算法 | 第四章:字符串
本文参考网课为 数据结构与算法 1 第四章字符串,主讲人 张铭 、王腾蛟 、赵海燕 、宋国杰 、邹磊 、黄群。 本文使用IDE为 Clion,开发环境 C14。 更新:2023 / 11 / 12 数据结构与算法 | 第四章:字符串 字符串概念字符串字符字符…...
2023-11-rust-struct
struct 类似 schema。 ts的interface 和type struct MyStruct {width: i32,height: i32, } 创建实例 let eg1 MyStruct {width: 23,height: 22,}; struct 可以有自己的方法,并且默认第一个参数是该实例 impl MyStruct {fn can_hold(&self, instance: &…...
Docker容器编排
文章目录 基本概念Docker ComposeSwarm分布式NodeTaskservice集群搭建弹性伸缩 基本概念 针对容器生命周期的管理,对容器生命周期进行更方便更快捷的方式进行管理。 依赖管理:当一个容器必须在另一个容器运行完成后,才能运行时,…...
计算机中丢失mfc140u.dll怎么解决
mfc140u.dll是一个Microsoft Visual C库文件,主要用于MFC(Microsoft Foundation Class)应用程序的开发。它包含了MFC应用程序所需的一些常用功能,如对话框、窗口、菜单等。当mfc140u.dll丢失时,可能会导致MFC应用程序无…...
postman设置动态token, 每次登录更新token
postman设置动态token, 每次登录更新token 文章目录 postman设置动态token, 每次登录更新token问题1. 设置全局变量2. 新建登录接口3. 设置脚本4. 切换环境5. 配置动态token 问题 token过期时间一般比较短, 每次使用postman调用接口都token非常麻烦 实现token过期后, 调用一次…...
架构师范文(AI写作)两篇
请点击↑关注、收藏,本博客免费为你获取精彩知识分享!有惊喜哟!! 架构师范文-论区块链技术及应用 2022年3月,我参与了某集团内部一款基于区块链技术的数字资产管理平台,该平台是为了方便管理公司旗下的各种…...
基于SSM的电子病历系统
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…...
一次sougo workflow库的使用过程
安装就是常规的make install tutorial http_echoserver实现一下,在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是苹果推出的最新操作系统,具有以下特点: 全新的设计风格:Big Sur采用了全新的设计语言,包括更加圆润的窗口和控件、更加鲜明的色彩和更加简洁的界面。这种设计风格使得操作系统更加美观和易用。强大的性能表现&…...
泛微E-Office信息泄露漏洞复现
简介 Weaver E-Office是中国泛微科技(Weaver)公司的一个协同办公系统。 Weaver E-Office 9.5版本存在安全漏洞。攻击者利用该漏洞可以访问文件或目录。 漏洞编号:CVE-2023-2766 漏洞复现 FOFA语法: app"泛微-EOffice&qu…...
-bash: sudo: command not found的解决方法
在 Linux 系统中,使用 sudo 命令时提示 “command not found”,首先执行以下命令看一下 /etc/sudoers.d 文件是否存在: find /etc/sudoers.d1)如果返回 No such file or directory,就说明系统没有安装sudo,…...
CMOS介绍
1 二极管 2 CMOS 2.1 栅极、源极、漏极 2.2 内部结构 2.2 导电原理 - 原理:1.通过门级和衬底加一个垂直电场Ev,从而在两口井之间形成反形层2.如果加的电场足够强,反形层就可以把source(源极)和drain(漏极…...
《软件工程与计算》期末考试真题范例及答案
今天分享一套针对《软件工程与计算》这本书的真题案例,有关《软件工程与计算》23章内容的重点知识整理,已经总结在了博客专栏中,有需要的自行阅读: 《软件工程与计算》啃书总结https://blog.csdn.net/jsl123x/category_12468792.…...
springboot高校全流程考勤系统-计算机毕设 附源码 27637
Springboot高校全流程考勤系统 摘 要 本文针对高校考勤等问题,对其进行研究分析,然后开发设计出高校全流程考勤系统以解决问题。高校全流程考勤系统系统主要功能模块包括:考勤签到、课程信息、考勤情况、申请记录列表等,系统功能设…...
大二第四周总结——用原生js封装一个分页器
用原生js封装一个分页器 起因:这次项目还是用原生的js来写的,我负责的是后台,分页是后台最常见的一个功能了,于是干脆封装一下,废话少说,直接上代码 这里是基本的样式 .pagination {display: flex;width: 600px;hei…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
