SpringBoot系列-2 自动装配

news2024/12/24 9:34:40

背景:

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中方法进行,读者可自行阅读。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

macOS使用conda初体会

最近在扫盲测序的一些知识 其中需要安装一些软件进行练习&#xff0c;如质控的fastqc&#xff0c;然后需要用conda来配置环境变量和安装软件。记录一下方便后续查阅学习 1.安装miniconda 由于我的电脑之前已经安装了brew&#xff0c;所以我就直接用brew安装了 brew install …

【yolov5】onnx的INT8量化engine

GitHub上有大佬写好代码&#xff0c;理论上直接克隆仓库里下来使用 git clone https://github.com/Wulingtian/yolov5_tensorrt_int8_tools.git 然后在yolov5_tensorrt_int8_tools的convert_trt_quant.py 修改如下参数 BATCH_SIZE 模型量化一次输入多少张图片 BATCH 模型量化…

Technology Strategy Patterns 学习笔记8- Communicating the Strategy-Decks(ppt模板)

1 Ghost Deck/Blank Deck 1.1 It’s a special way of making an initial deck that has a certain purpose 1.2 you’re making sure you have figured out what all the important shots are before incurring the major expense of shooting them 1.3 需要从技术、战略、产…

2023 年最新企业微信官方会话机器人开发详细教程(更新中)

目标是开发一个简易机器人&#xff0c;能接收消息并作出回复。 获取企业 ID 企业信息页面链接地址&#xff1a;https://work.weixin.qq.com/wework_admin/frame#profile 自建企业微信机器人 配置机器人应用详情 功能配置 接收消息服务器配置 配置消息服务器配置 配置环境变量…

[01]汇川IMC30G-E系列运动控制卡应用笔记

简介 IMC30G-E系列产品是汇川技术自主研制的高性能EtherCAT网络型运动控制器&#xff08;卡&#xff09;&#xff0c;同时兼容脉冲轴的控制&#xff1b;IMC30G-E支持点位/JOG、插补、多轴同步、高速位置比较输出、PWM等全面的运动控制功能&#xff0c;具备高同步控制精度。 开发…

OpenWRT浅尝 / 基于RAVPower-WD009便携路由文件宝的旁路网关配置

目录 前言需求分析手头的设备家庭网络拓扑图旁路网关配置OpenWRT固件选择OpenWRT固件刷入旁路网关配置流程 旁路网关的使用前置工作日常存储/关键备份内网穿透24小时待命下载器 前言 近期由于个人需求&#xff0c;需要一台OpenWRT设备实现一些功能。所以本文主要还是为了自己后…

k8s-实验部署 1

1、k8s集群部署 更改所有主机名称和解析 开启四台实验主机&#xff0c;k8s1 仓库&#xff1b;k8s2 集群控制节点&#xff1b; k8s3 和k8s4集群工作节点&#xff1b; 集群环境初始化 使用k8s1作为仓库&#xff0c;将所有的镜像都保存在本地&#xff0c;不要将集群从外部走 仓库…

金和OA jc6 任意文件上传漏洞复现

0x01 产品简介 金和OA协同办公管理系统软件&#xff08;简称金和OA&#xff09;&#xff0c;本着简单、适用、高效的原则&#xff0c;贴合企事业单位的实际需求&#xff0c;实行通用化、标准化、智能化、人性化的产品设计&#xff0c;充分体现企事业单位规范管理、提高办公效率…

学习率范围测试(LR Finder)脚本

简介 深度学习中的学习率是模型训练中至关重要的超参数之一。合适的学习率可以加速模型的收敛&#xff0c;提高训练效率&#xff0c;而不恰当的学习率可能导致训练过慢或者无法收敛。为了找到合适的学习率&#xff0c;LR Finder成为了一种强大的工具。 学习率范围测试&#x…

Django的ORM操作

文章目录 1.ORM操作1.1 表结构1.1.1 常见字段和参数1.1.2 表关系 2.ORM2.1 基本操作2.2 连接数据库2.3 基础增删改查2.3.1 增加2.3.2 查找2.3.4 删除2.3.4 修改 1.ORM操作 orm&#xff0c;关系对象映射&#xff0c;本质翻译的。 1.1 表结构 实现&#xff1a;创建表、修改表、…

思维模型 暗示效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。无形中引导他人的思想和行为。 1 暗示效应的应用 1.1 暗示效应在商业品牌树立中的应用 可口可乐的品牌形象&#xff1a;可口可乐通过广告、包装和营销活动&#xff0c;向消费者传递了一种…

【递归】求根节点到叶节点数字之和(Java版)

目录 1.题目解析 2.讲解算法原理 3.代码 1.题目解析 LCR 049. 求根节点到叶节点数字之和 给定一个二叉树的根节点 root &#xff0c;树中每个节点都存放有一个 0 到 9 之间的数字。 每条从根节点到叶节点的路径都代表一个数字&#xff1a; 例如&#xff0c;从根节点到叶节点…

伙伴(buddy)系统原理

一、伙伴算法的由来 在实际情况中&#xff0c;操作系统必须能够在任意时刻申请和释放任意大小的内存&#xff0c;该函数的实现需要考虑延时问题和碎片问题。 延时问题指的是系统查找到可分配单元的时间变长&#xff0c;例如程序请求分配一个64KB的内存空间&#xff0c;系统查看…

Technology Strategy Patterns 学习笔记9 - bringing it all together

1 Patterns Map 2 Creating the Strategy 2.1 Ansoff Growth Matrix 和owth-share Matrix 区别参见https://fourweekmba.com/bcg-matrix-vs-ansoff-matrix/ 3 Communicating

STM32F407: CMSIS-DSP库的移植(基于库文件)

目录 1. 源码下载 2. DSP库源码简介 3.基于库的移植(DSP库的使用) 3.1 实验1 3.2 实验2 4. 使用V6版本的编译器进行编译 上一篇&#xff1a;STM32F407-Discovery的硬件FPU-CSDN博客 1. 源码下载 Github地址&#xff1a;GitHub - ARM-software/CMSIS_5: CMSIS Version 5…

开发者测试2023省赛--Square测试用例

测试结果 官方提交结果 EclEmma PITest 被测文件 [1/7] Square.java /*** This class implements the Square block cipher.** <P>* <b>References</b>** <P>* The Square algorithm was developed by <a href="mailto:Daemen.J@banksys.co…

AWS云服务器EC2实例进行操作系统迁移

AWS云服务器EC2实例进行操作系统迁移 文章目录 AWS云服务器EC2实例进行操作系统迁移1. 亚马逊EC2云服务器简介1.2 亚马逊EC2云务器与弹性云服务器区别 2. 亚马逊EC2云服务器配置流程2.1 亚马逊EC2云服务器实例配置2.1.1 EC2实例购买教程2.1.1 EC2实例初始化配置2.1.2 远程登录E…

Gold-YOLO:基于收集-分配机制的高效目标检测器

文章目录 摘要1、简介2、相关工作2.1、实时目标检测器2.2、基于Transformer的目标检测2.3、用于目标检测的多尺度特征 3、方法3.1、预备知识3.2、低级收集和分发分支3.3、高阶段收集和分发分支3.4、增强的跨层信息流3.5、遮罩图像建模预训练 4、实验4.1、设置4.2、比较4.3.2、 …

recycleView(三)动态修改背景色

效果图 1.关键代码 1. // 定义一个变量来记录滑动的距离var scrollDistance 0// 在RecycleView的滑动监听器中更新滑动的距离binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {override fun onScrolled(recyclerView: RecyclerView, …

Pinia 状态管理器 菠萝

Pinia介绍&#xff1a; Pinia 是 Vue 的专属状态管理库&#xff0c;它允许你跨组件或页面共享状态。 Pinia 大小只有 1kb 左右&#xff0c;超轻量级&#xff0c;你甚至可能忘记它的存在&#xff01; 相比 Vuex,Pinia 的优点&#xff1a; 更贴合 Vue 3 的 Composition API 风…