【Spring专题】Spring之Bean的生命周期源码解析——上(扫描生成BeanDefinition)

目录

  • 前言
    • 阅读指引
    • 阅读建议
  • 课程内容
    • 一、生成BeanDefinition
      • 1.1 简单回顾
      • 1.2 概念回顾
      • 1.3 核心方法讲解
    • 二、方法讲解
      • 2.1 ClassPathBeanDefinitionScanner#scan
      • 2.2 ClassPathBeanDefinitionScanner#doScan
      • 2.3 ClassPathScanningCandidateComponentProvider#findCandidateComponents
      • 2.4 ClassPathScanningCandidateComponentProvider#scanCandidateComponents
      • 2.5 ClassPathScanningCandidateComponentProvider#isCandidateComponent
      • 2.6 ClassPathScanningCandidateComponentProvider#isCandidateComponent
      • 2.7 ClassPathBeanDefinitionScanner#postProcessBeanDefinition
      • 2.8 AnnotationConfigUtils.processCommonDefinitionAnnotations
      • 2.9 ClassPathBeanDefinitionScanner#checkCandidate
      • 2.10 DefaultListableBeanFactory#registerBeanDefinition
    • 三、扫描逻辑流程图
    • 四、合并BeanDefinition
  • 学习总结

前言

阅读指引

Spring最重要的功能就是帮助程序员创建对象(也就是IOC),而启动Spring就是为创建Bean对象做准备,所以我们先明白Spring到底是怎么去创建Bean的,也就是先弄明白Bean的生命周期。
Bean的生命周期就是指:在Spring中,一个Bean是如何生成的,如何销毁的。
本节课的内容,将会以下面这段代码为入口讲解:

public class MyApplicationTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("org.tuling.spring");Object newUser = context.getBean("newUser");System.out.println(newUser);}
}

主要用到的是如下Spring容器的构造方法:

/*** 创建一个新的AnnotationConfigApplicationContext,扫描给定包中的组件,为这些组件注册bean定义,并自动刷新上下文。* 参数:* baseppackages——要扫描组件类的包*/
public AnnotationConfigApplicationContext(String... basePackages) {this();scan(basePackages);refresh();
}

阅读建议

  1. 看源码,切记纠结细枝末节,不然很容易陷进去。正常来说,看主要流程就好了
  2. 遇到不懂的,多看看类注释或者方法注释。Spring这种优秀源码,注释真的非常到位
  3. 如果你是idea用户,多用F11的书签功能。
    • Ctrl + F11 选中文件 / 文件夹,使用助记符设定 / 取消书签 (必备)
    • Shift + F11 弹出书签显示层 (必备)
    • Ctrl +1,2,3…9 定位到对应数值的书签位置 (必备)

课程内容

一、生成BeanDefinition

1.1 简单回顾

如果大家看过我前面的文章,应该会知道,BeanDefiniton的生成,是在生产Bean之前的【扫描】阶段。如下图所示:
在这里插入图片描述

不过咱也说过,这只是简单实现而已,事实上,Spring实现这个扫描过程,涉及到了3个核心类,9个核心方法!

1.2 概念回顾

在这个【扫描】过程中,涉及到了一些Spring底层设计的概念,我在上一个笔记里面有大概介绍过,不记得的同学记得回去翻一翻。
主要涉及的概念有:

  • BeanDefinition(设计图纸):BeanDefinition表示Bean定义,BeanDefinition中存在很多属性用来描述一个Bean的特征
  • ClassPathBeanDefinitionScanner(图纸注册器):用于注册图纸
  • BeanFacotory(Bean工厂):生产Bean。不过实际上,这里用到DefaultListableBeanFactory这个类的BeanDefinitionRegistry接口的注册能力

文章链接:
《【Spring专题】Spring之底层架构核心概念解析》

1.3 核心方法讲解

我们在前面提到过,整个扫描流程会涉及到【3个核心类,10个核心方法】。下面,我们将会按照调用次序,依次讲解各个方法。

二、方法讲解

整个扫描流程的入口,是下面代码中的scan()方法,而最终调用的是ClassPathBeanDefinitionScanner.scan()

/*** 创建一个新的AnnotationConfigApplicationContext,扫描给定包中的组件,为这些组件注册bean定义,并自动刷新上下文。* 参数:* baseppackages——要扫描组件类的包*/
public AnnotationConfigApplicationContext(String... basePackages) {this();scan(basePackages);refresh();
}public void scan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan").tag("packages", () -> Arrays.toString(basePackages));this.scanner.scan(basePackages);scanPackages.end();
}

2.1 ClassPathBeanDefinitionScanner#scan

全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
方法注释:在指定的基本包中执行扫描

源码如下:

	public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();doScan(basePackages);// Register annotation config processors, if necessary.if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);}

但其实真正干活的还不是这里,而是里面的doScan()

2.2 ClassPathBeanDefinitionScanner#doScan

方法调用链:由 2.1中的scan()调用进来
全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
方法注释:在指定的基包中执行扫描,返回已注册的bean定义。此方法不注册注释配置处理程序,而是将此工作留给调用者。

源码如下:

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();for (String basePackage : basePackages) {// 寻找候选的BeanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);// 判断是否要设置BeanDefinition属性if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}// 判断是否要设置通用的注解BeanDefiniton属性if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}// 判断是否注册BeanDefinitionif (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

方法解读:这个代码看似很多,其实整体来说比较简单,分为4个步骤

  1. 先扫描classpath下所有的class,寻找候选的BeanDefinition。至于什么是候选的,后面会讲解
  2. 遍历找到的候选BeanDefinition,判断是否需要设置BeanDefinition的一些属性。比如默认属性等
  3. 判断是否要设置通用的注解BeanDefiniton属性
  4. 判断是否要注册BeanDefinition,毕竟可能出现重复的BeanDefinition

2.3 ClassPathScanningCandidateComponentProvider#findCandidateComponents

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
方法注释:扫描类路径上的候选Component。

源码如下:

	public Set<BeanDefinition> findCandidateComponents(String basePackage) {if (this.componentsIndex != null && indexSupportsIncludeFilters()) {return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);}else {return scanCandidateComponents(basePackage);}}

方法解读:这里有一个if-else,但通常都是进入elseif里面的情况我们在业务开发中基本不会用到,这是一个为了加快扫描包的策略,通过使用索引思想。怎么实现呢?就是在resource下新建一个spring.components配置文件(K-V格式)。如下:

org.tuling.spring.bean.User = org.springframework.stereotype.Component

通过直接告诉Spring哪些是候选的Component,免除了扫描所有class文件筛选的过程,从而提升了效率。

2.4 ClassPathScanningCandidateComponentProvider#scanCandidateComponents

方法调用链:由 2.3中的findCandidateComponents()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
方法注释:扫描类路径上的候选Component。

源码如下:

    private Set<org.springframework.beans.factory.config.BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}try {// 读取类元信息MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// 第一层判断是否为候选组件,判断核心为:是否需要过滤if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);// 第二层判断是否为候选组件,判断核心为:是否独立、抽象类等if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);}else {if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}}catch (FileNotFoundException ex) {if (traceEnabled) {logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());}}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}

方法解读:代码看着很长,实际上很简单。主要分为4个步骤

  1. 读取类元信息(从磁盘中读取)
    • 首先,通过ResourcePatternResolver获得指定包路径下的所有.class文件(Spring源码中将此文件包装成了Resource对象)
    • 遍历每个Resource对象
    • 利用MetadataReaderFactory解析Resource对象得到MetadataReader(在Spring源码中MetadataReaderFactory具体的实现类为CachingMetadataReaderFactory,MetadataReader的具体实现类为SimpleMetadataReader)
  2. 判断是否为候选组件,判断核心为:是否需要过滤(第一个isCandidateComponent
    • 利用MetadataReader进行excludeFilters和includeFilters,以及条件注解@Conditional的筛选(条件注解并不能理解:某个类上是否存在@Conditional注解,如果存在则调用注解中所指定的类的match方法进行匹配,匹配成功则通过筛选,匹配失败则pass掉。)
    • 筛选通过后,基于metadataReader生成ScannedGenericBeanDefinition
  3. 判断是否为候选组件,判断核心为:是否独立、抽象类等。(第二个isCandidateComponent)(注意:他跟上面的isCandidateComponent不是同一个东西,属于方法重载)
  4. 如果筛选通过,那么就表示扫描到了一个Bean,将ScannedGenericBeanDefinition加入结果集

MetadataReader表示类的元数据读取器,主要包含了一个AnnotationMetadata,功能有

  • 获取类的名字、
  • 获取父类的名字
  • 获取所实现的所有接口名
  • 获取所有内部类的名字
  • 判断是不是抽象类
  • 判断是不是接口
  • 判断是不是一个注解
  • 获取拥有某个注解的方法集合
  • 获取类上添加的所有注解信息
  • 获取类上添加的所有注解类型集合

2.5 ClassPathScanningCandidateComponentProvider#isCandidateComponent

方法调用链:由 2.4中的scanCandidateComponents()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent
方法注释:确定给定的类是否不匹配任何排除筛选器,而匹配至少一个包含筛选器。

源码如下:

	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return isConditionMatch(metadataReader);}}return false;}

方法解读:这里只是校验,当前类,是否在过滤器排除选项以内。我估计很多朋友想到的都是@ComponentScan注解里面的includeFilterexcludeFilter属性。只能说不完全正确。
因为,在我们启动容器创建ClassPathBeanDefinitionScanner也会添加默认的过滤策略。如下所示:

/*** 注册@Component的默认过滤器。* 这将隐式注册所有带有@Component元注释的注释,包括@Repository、@Service和@Controller构造型注释。* 还支持Java EE 6的javax.annotation.ManagedBean和JSR-330的javax.inject.Named注释(如果可用)。*/
protected void registerDefaultFilters() {this.includeFilters.add(new AnnotationTypeFilter(Component.class));ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.}try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.}
}

2.6 ClassPathScanningCandidateComponentProvider#isCandidateComponent

方法调用链:由 2.4中的scanCandidateComponents()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent
方法注释:确定给定的bean定义是否符合候选条件。默认实现检查类是否不是接口,是否依赖于封闭类。

源码如下:

/***确定给定的bean定义是否符合候选条件。* 默认实现检查类是否不是接口,是否依赖于封闭类。* 可以在子类中重写。*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();return (metadata.isIndependent() && (metadata.isConcrete() ||(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

方法解读:这里有2个比较陌生的,那就是,什么是独立类,以及Lookup注解。

  1. 独立类,及不是普通内部类(静态内部类是独立类)
  2. Lookup注解,自己去百度使用方法

总结方法,判断是候选Component的条件是:

  1. 首先是独立类
  2. 要么是具体的子类,要么就是:有Lookup注解的抽象类

2.7 ClassPathBeanDefinitionScanner#postProcessBeanDefinition

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition
方法注释:扫描类路径上的候选Component。

源码如下:

/*** 除了通过扫描组件类检索到的内容之外,还可以对给定的bean定义应用进一步的设置。*/
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {beanDefinition.applyDefaults(this.beanDefinitionDefaults);if (this.autowireCandidatePatterns != null) {beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));}}

方法解读:这里主要还初始化了BeanDefinition一些默认属性beanDefinition.applyDefaults(this.beanDefinitionDefaults);
源码如下:

	public void applyDefaults(BeanDefinitionDefaults defaults) {Boolean lazyInit = defaults.getLazyInit();if (lazyInit != null) {setLazyInit(lazyInit);}setAutowireMode(defaults.getAutowireMode());setDependencyCheck(defaults.getDependencyCheck());setInitMethodName(defaults.getInitMethodName());setEnforceInitMethod(false);setDestroyMethodName(defaults.getDestroyMethodName());setEnforceDestroyMethod(false);}

2.8 AnnotationConfigUtils.processCommonDefinitionAnnotations

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.AnnotationConfigUtils#processCommonDefinitionAnnotations
方法注释:根据注解累类元信息,设置BeanDefinition注解相关属性。

源码如下:

public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {processCommonDefinitionAnnotations(abd, abd.getMetadata());
}static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}else if (abd.getMetadata() != metadata) {lazy = attributesFor(abd.getMetadata(), Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}}if (metadata.isAnnotated(Primary.class.getName())) {abd.setPrimary(true);}AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);if (dependsOn != null) {abd.setDependsOn(dependsOn.getStringArray("value"));}AnnotationAttributes role = attributesFor(metadata, Role.class);if (role != null) {abd.setRole(role.getNumber("value").intValue());}AnnotationAttributes description = attributesFor(metadata, Description.class);if (description != null) {abd.setDescription(description.getString("value"));}}

没啥好解读的,很简单的判断

2.9 ClassPathBeanDefinitionScanner#checkCandidate

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate
方法注释:检查给定的候选bean名称,确定是否需要注册相应的bean定义,或者是否与现有定义冲突。

源码如下:

protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {if (!this.registry.containsBeanDefinition(beanName)) {return true;}BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();if (originatingDef != null) {existingDef = originatingDef;}if (isCompatible(beanDefinition, existingDef)) {return false;}throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");}

方法解读:这里正常来说,要么返回true,要么就是重复定义相同名字的Bean然后抛出异常。直接返回false,通常是存在父子容器的情况下。

2.10 DefaultListableBeanFactory#registerBeanDefinition

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
方法注释:用这个注册中心注册一个新的bean定义。必须支持RootBeanDefinition和ChildBeanDefinition。

源码如下:

@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);if (existingDefinition != null) {if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}else if (existingDefinition.getRole() < beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (logger.isInfoEnabled()) {logger.info("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +existingDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(existingDefinition)) {if (logger.isDebugEnabled()) {logger.debug("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}else {if (logger.isTraceEnabled()) {logger.trace("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}this.beanDefinitionMap.put(beanName, beanDefinition);}else {if (hasBeanCreationStarted()) {// Cannot modify startup-time collection elements anymore (for stable iteration)synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;removeManualSingletonName(beanName);}}else {// Still in startup registration phasethis.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}else if (isConfigurationFrozen()) {clearByTypeCache();}}

方法解读:这个就稍微复杂一点了,我也没细看。而且,这个隐藏的挺深的,虽然是从doScan()里面进调用的,但是本质上是调用AnnotationConfigApplicationContext继承自GenericApplicationContextregisterBeanDefinition方法

三、扫描逻辑流程图

在这里插入图片描述

四、合并BeanDefinition

到了后面的IOC过程,生成Bean的时候,我们会看到很多这样的调用:

RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

这个就是所谓的,获取合并BeanDefinition。什么是合并BeanDefinition?其实就是父子BeanDefinition。跟Java里面的继承是一样一样的。但是,这个通常发生在xml配置bean里面,如下:

<bean id="parent" class="com.zhouyu.service.Parent" scope="prototype"/>
<bean id="child" class="com.zhouyu.service.Child"/>

但是这么定义的情况下,child就是原型Bean了。因为child的父BeanDefinition是parent,所以会继承parent上所定义的scope属性。而在根据child来生成Bean对象之前,需要进行BeanDefinition的合并,得到完整的child的BeanDefinition。

学习总结

  1. 学习了Spring源码流程中的【扫描】底层原理

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

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

相关文章

面试热题(环形链表II)

给定一个链表&#xff0c;返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环&#xff0c;则返回 null。 为了表示给定链表中的环&#xff0c;我们使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索引…

STM32CubeMX+VSCODE+EIDE+RT-THREAD 工程创建

Eide环境搭建暂且不表&#xff0c;后续补充。主要记录下Vscode环境下 创建Rt-thread工程的过程。分别介绍STM32CubeMX添加rtt支持包的方式和手动添加rtt kernel方式。STM32CubeMX生成工程的时候有"坑"&#xff0c;防止下次忘记&#xff0c;方便渡一下有缘人&#xff…

【力扣】2810. 故障键盘 <模拟>

【力扣】2810. 故障键盘 你的笔记本键盘存在故障&#xff0c;每当你在上面输入字符 ‘i’ 时&#xff0c;它会反转你所写的字符串。而输入其他字符则可以正常工作。给你一个下标从 0 开始的字符串 s &#xff0c;请你用故障键盘依次输入每个字符。返回最终笔记本屏幕上输出的字…

MyCat配置rule.xml、server.xml讲解

1. rule.xml分片规则配置文件 rule.xml中配置的主要就是拆分表的规则&#xff0c;rule.xml中主要包含两类标签 tableRule 和Function。 tableRule标签里面主要配置我们的分片规则&#xff0c;Function里面涉及的是分片规则里面所涉及的java类&#xff0c;都是在function中配置…

OI易问卷协助企业服务好员工,收集员工反馈与信息

OI易问卷——企业问卷调查工具 OI易问卷&#xff0c;是群硕专为企业打造&#xff0c;对内服务员工的调查问卷。 集成于办公联合创新平台&#xff0c;并进一步帮助客户实现与微信或企业微信等其他平台的对接。 可以有效促进员工服务数字化&#xff0c;提高各部门工作效率&…

深度优先搜索DFS搜索数及B3621 枚举元组 B3622 枚举子集

深度优先搜索DFS搜索 DFS&#xff1a;从根节点出发&#xff0c;寻找子节点&#xff0c;到最深层返回到上一层继续递归搜索。 B3621 枚举元组 题目传送 题面 题目描述 n 元组是指由 n 个元素组成的序列。例如 (1,1,2) 是一个三元组、(233,254,277,123)是一个四元组。 给定…

牛客网【面试必刷TOP101】~ 06 递归/回溯

牛客网【面试必刷TOP101】~ 06 递归/回溯 文章目录 牛客网【面试必刷TOP101】~ 06 递归/回溯[toc]BM55 没有重复项数字的全排列(★★)BM56 有重复项数字的全排列(★★)BM57 岛屿数量(★★)BM58 字符串的排列(★★)BM59 N皇后问题(★★★)BM60 括号生成(★★)BM61 矩阵最长递增路…

中介者模式(C++)

定义 用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖)&#xff0c;从而使其耦合松散(管理变化)&#xff0c;而且可以独立地改变它们之间的交互。 应用场景 在软件构建过程中&#xff0c;经常会出现多个对象…

C#实现三菱FX-3U SerialOverTcp

设备信息 测试结果 D值测试 Y值写入后读取测试 协议解析 三菱FX 3U系列PLC的通信协议 1. 每次给PLC发送指令后&#xff0c;必须等待PLC的应答完成才能发送下一条指令; 2. 报文都是十六进制ASCII码的形式 3. 相关指令 指令 命令码&#xff08;ASCII码&#xff09; 操作原件 …

Selenium的xpath高级写法-实用篇

系列文章目录 提示&#xff1a;阅读本章之前&#xff0c;请先阅读目录 文章目录 系列文章目录前言获取父级获取前一个兄弟级获取后一个兄弟级获取内容包含某些内容获取内容是空消除不可见的内容 前言 获取父级 //div[text()‘我是子级’]/parent::div[text()‘我是父级’] 获取…

怎样制定测试计划和设计测试用例?

测试工作贯穿于整个软件开发生命周期&#xff0c;是一项庞大而复杂的工作&#xff0c;需要制订一个完整且详细的测试计划作为指导。测试计划是整个测试工作的导航图&#xff0c;但它并不是一成不变的&#xff0c;随着项目推进或需求变更&#xff0c;测试计划也会不断发生改变&a…

06 - Stream如何提高遍历集合效率?

前面我们讲过 List 集合类&#xff0c;那我想你一定也知道集合的顶端接口 Collection。 在 Java8 中&#xff0c;Collection 新增了两个流方法&#xff0c;分别是 Stream() 和 parallelStream()。 1、什么是 Stream&#xff1f; 现在很多大数据量系统中都存在分表分库的情况…

vue3部署宝塔后请求接口404以及刷新页面404的问题解决方案

技术&#xff1a;vue3.2.13 UI框架:element-plus 2.1.1 服务器&#xff1a;腾讯云Centos系统宝塔面板 Nginx:1.22.1 前提&#xff1a;宝塔面板》网站PHP项目 已经新建了项目 1.处理vue页面刷新报错404 如上图&#xff0c;在PHP网站》设置》配置文件&#xff0c;新增如下代码…

Vue VS React:两大前端框架的对比与分析

Vue和React是当前最流行的前端框架之一&#xff0c;它们都有着广泛的应用和开发者社区。下面是Vue和React之间的深度对比与分析&#xff1a; 学习曲线&#xff1a; Vue&#xff1a;Vue拥有简单直观的API和文档&#xff0c;对初学者友好。Vue的设计初衷是逐步增强的&#xff0c;…

springboot 多模块 每个模块进行单独打包

springboot项目目录结构 打包模块需要进行的配置 配置文件引入打包插件 <build><finalName>api</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifac…

篇八:装饰器模式:动态增加功能

篇八&#xff1a;“装饰器模式&#xff1a;动态增加功能” 开始本篇文章之前先推荐一个好用的学习工具&#xff0c;AIRIght&#xff0c;借助于AI助手工具&#xff0c;学习事半功倍。欢迎访问&#xff1a;http://airight.fun/。 另外有2本不错的关于设计模式的资料&#xff0c…

github访问加速

# 解决GitHub无法访问140.82.113.3 github.com 140.82.114.20 gist.github.com151.101.184.133 assets-cdn.github.com 151.101.184.133 raw.githubusercontent.com 151.101.184.133 gist.githubusercontent.com 151.101.184.133 cloud.githubusercontent…

css3 实现文字横幅无缝滚动

css3 实现文字横幅无缝滚动 使用 css3 关键帧 keyframes 和 animation 属性实现文字横幅无缝滚动。 <template><div class"skiHallBanner"><div class"skiHallBanner-text"><span>{{ text }}</span></div></div>…

网站架构部署

网站架构部署 网站部署 LAMP架构介绍 linuxapache&#xff08;nginx&#xff09; 提供静态资源展示转发请求给后端程序…… mysql 数据信息…… php {PHP Module}…… 快速部署LAMP架构 [rootllz ~]# iptables -F [rootllz ~]# systemctl stop firewalld [rootllz ~]# sy…

推荐几个好玩的网页版联机小游戏

generals.io 点此进入 网格地图&#xff0c;设定简单&#xff0c;兵力只有数字&#xff0c;玩家控制自己的兵力扩张领地&#xff0c;击杀其他玩家&#xff0c;但是很好玩&#xff0c;还可以自定义地图 基本上都是国人 richup.io 点此进入 类似于大富翁游戏 基本上都是外国人…