springboot创建并配置环境(三) - 配置扩展属性(上集)

news2024/9/29 9:29:59

文章目录

  • 一、介绍
  • 二、配置文件application.yml

一、介绍

在上一篇文章:springboot创建并配置环境(二) - 配置基础环境中,我们介绍了springboot如何配置基础环境变量。本篇文章讨论如何处理配置文件。即来自不同位置的配置属性,如:classpath路径下的**application.ymlbootstrap.yml使用@PropertySource注解指定的文件、以及来自项目外部的配置文件**等。

二、配置文件application.yml

我们在项目的resources目录下新建配置文件application.yml,并添加如下配置

在这里插入图片描述

然后进入断点调试。

当代码执行到下面这一行时,我们查看此时环境实例中保存的配置属性都是前面我们讲过的,来自配置文件中的属性还没有被保存。

在这里插入图片描述

当我们进行到下一行时,此时来自配置文件中的属性就已经被保存到环境中来了

在这里插入图片描述

很明显,springboot通过观察者模式发布一个环境准备就绪事件,由监听该事件的监听器处理不同的逻辑,即以下代码

// 通过观察者模式发布一个环境准备就绪事件,由监听该事件的监听器处理不同的逻辑
listeners.environmentPrepared(bootstrapContext, environment);

我们进入environmentPrepared()方法一探究竟,该方法源码如下所示,大致意思就是向this.listeners传入Consumer对象,由this.listeners中的监听器来执行这个Consumer对象。

在这里插入图片描述

插入一嘴,this.listenersSpringApplicationRunListener运行时监听器的集合,如下所示

在这里插入图片描述

该集合是进入run()方法后执行的第二个关键步骤,第一个关键步骤就是创建BootstrapContext上下文。

在这里插入图片描述

而对SpringApplicationRunListener运行时监听器的集合的获取则是从META-INF目录下的spring.factories文件中获取的。

在这里插入图片描述

运行时监听器的集合中内置的该监听器只有一种,即EventPublishingRunListener事件发布监听器,该监听器专门用来发布事件。而我们当前正要发布一个环境准备就绪事件

而且我们在调试代码过程中也看到了,此时需要发布的是环境准备就绪事件,调用的是监听器的environmentPrepared()方法,所以我们进入EventPublishingRunListener监听器的这个方法

在这里插入图片描述

此时才真正的创建了环境准备就绪事件的实例ApplicationEnvironmentPreparedEvent,并通过this.initialMulticaster广播该事件。其实这里涉及到的只是springboot对观察者模式的实现。但是为了进一步摸清整个环境配置过程的来龙去脉,也无所谓了。

下面我们进入multicastEvent()方法来看看是如何广播的。

在这里插入图片描述

该方法通过getApplicationListeners()方法获取项目中监听当前事件的所有监听器,并对其进行遍历,然后不同的监听器针对该事件进行不同的逻辑处理。

在这里插入图片描述

从上面的截图中我们发现,针对环境准备就绪事件的监听器有6个,他们监听到该事件后处理不同的逻辑。

  • EnvironmentPostProcessorApplicationListener:使用环境后处理器对环境进行配置
  • AnsiOutputApplicationListener:控制日志的颜色,决定输出的日志文本是否具有颜色
  • LoggingApplicationListener:对日志进行配置
  • BackgroundPreinitializer:提前初始化耗时任务的后台线程
  • DelegatingApplicationListener:将监听到的事件再次发布,由指定的监听器执行
  • FileEncodingApplicationListener:如果系统文件编码与环境中设置的期望值不匹配,则立刻停止应用程序的启动

从上面的说明来看,我们当前关注的是如何将配置文件中的配置信息加载到环境中,因此我们只需要关注EnvironmentPostProcessorApplicationListener在监听到环境准备就绪事件后执行什么处理逻辑即可。

下面我们进入监听器EnvironmentPostProcessorApplicationListener,在springboot中,监听器通过onApplicationEvent()方法监听事件,该方法如下所示

在这里插入图片描述

前面讲过,当前程序发布的事件为环境准备就绪事件ApplicationEnvironmentPreparedEvent,所以显然我们将目光放在onApplicationEnvironmentPreparedEvent()方法上来处理该事件。

在该处理方法中,根据当前事件中的bootstrapContext属性(也就是启动程序上下文)获取到对应的环境后处理器

我们简单看一下如何获取环境后处理器的,进入getEnvironmentPostProcessors()方法

在这里插入图片描述

从该方法中可以看到,它的实现是通过工厂模式从环境后处理器工厂获取到环境后处理器的。但是我们这一路走来,并不知道该工厂中有哪些后处理器,甚至该工厂是在什么时候实例化的都不知道。

首先我们看一下当前监听器EnvironmentPostProcessorApplicationListener的构造方法,从构造方法中寻找答案

在这里插入图片描述

答案已经揭晓,环境后处理器工厂对象的实例化是在此监听器的构造方法中完成的,它通过环境后处理器工厂EnvironmentPostProcessorsFactory的静态方法fromSpringFactories()实例化

在这里插入图片描述

另外,环境后处理器工厂EnvironmentPostProcessorsFactory在这里的实现类使用的是ReflectionEnvironmentPostProcessorsFactory,该实现类通过classNames属性保存着spring.factories文件中的所有环境后处理器的类路径,当需要从该工厂中获取环境后处理器时,该工厂通过反射获取环境后处理器的实例

回到正题,我们需要知道根据当前事件中的bootstrapContext属性(也就是启动程序上下文)获取到对应的环境后处理器有哪些,打断点进行代码调试,如下

在这里插入图片描述

由此可见,对启动环境的处理可不止是从配置文件中获取配置这么简单,springboot对环境的处理又细分为这么多种:

  • RandomValuePropertySourceEnvironmentPostProcessor:在环境中添加随机数的配置信息

  • SystemEnvironmentPropertySourceEnvironmentPostProcessor:将环境中以保存的系统环境变量相关的属性进行替换,将原本保存环境变量的SystemEnvironmentPropertySource实例替换成其子类OriginAwareSystemEnvironmentPropertySource

  • SpringApplicationJsonEnvironmentPostProcessor:将当前环境中已经保存的属性集合中出现的key为spring.application.jsonSPRING_APPLICATION_JSONvalue为json字符串的属性转换成map形式

  • CloudFoundryVcapEnvironmentPostProcessor:与远程配置中心相关。我们可以理解为从远程配置中心读取配置

  • ConfigDataEnvironmentPostProcessor:与配置数据相关。该处理器专门负责读取各个位置的配置文件中的配置信息。其实在springboot中,有另一个处理器ConfigFileApplicationListener(配置文件监听器),两者的作用相同,但是后者被springboot打上了@Deprecated,说明被启用了,想必从命名上来看后者是一个监听器,相比之下前者更适合。以下为后者配置文件监听器的注释

    // 从springboot2.4.0版本开始,
    // 弃用ConfigFileApplicationListener,
    // 使用ConfigDataEnvironmentPostProcessor
    Deprecated since 2.4.0 in favor of ConfigDataEnvironmentPostProcessor
    

接下来我们在来看ConfigDataEnvironmentPostProcessor(配置数据环境后处理器)postProcessEnvironment()方法实现

在这里插入图片描述

从该方法的源码中,我们发现它也没做什么特别重要的事,也没有对配置文件做出什么动作。

其实就只有三件事:

  • 初始化一个资源加载器。很明显,它用来加载resources目录下的配置文件资源。
  • 创建一个ConfigDataEnvironment对象。
  • 调用ConfigDataEnvironment对象的processAndApply()方法。

所以,我们把目光再次转向ConfigDataEnvironment类。该类有几个非常熟悉的常量,如下所示

在这里插入图片描述

从这几个常量中我们可以肯定,ConfigDataEnvironment类就是负责读取配置文件中的配置信息的类了。应该是重中之重了吧。

所以我们应当分两步分析此类:①分析构造函数。②分析processAndApply()方法

  • 构造函数

    在这里插入图片描述

    该构造方法对一大堆的属性进行了初始化(如上图所示),我们对其中两个属性的初始化做一个简单了解

    • binder属性:包含环境中的所有配置信息

      在上图中大致介绍了该属性的作用,下面我们看一下Binder.get()方法的源码

      在这里插入图片描述

      从上面源码中,我们可以看到,这里的binder对象是通过Binder中的静态方法get()以当前环境为参数去创建Binder实例的。Binder实例中包含了当前环境中key为configurationProperties的属性(其实就是所有的属性),以及解析以${}placeholder的属性的解析器。

      所以,在这里我们初步知道PropertySourcesPlaceholdersResolver是一个用来处理下图中firstName配置的解析器

      在这里插入图片描述

      因此,binder属性中包含了以下成分:

      ① 当前环境中的所有配置信息

      ② 处理配置文件中placeholder解析器

    • resolvers属性:配置文件位置解析器集合

      在初始化该属性时,我们看到是通过调用createConfigDataLocationResolvers()方法完成的。从方法名也可以知道,resolvers属性是各种配置文件位置的解析器(用来解析文件位置)。看一下该方法的实现:

      在这里插入图片描述

      下面我们就来看一下通过该方法获得的配置文件位置解析器的实例有哪些

      在这里插入图片描述

      这里我们简单介绍一下标准配置文件解析器StandardConfigDataLocationResolver

      该解析器内部维护了两个属性:

      static final String CONFIG_NAME_PROPERTY = "spring.config.name";
      private static final String[] DEFAULT_CONFIG_NAMES = { "application" };
      private final List<PropertySourceLoader> propertySourceLoaders;
      
      • spring.config.name:表示我们指定配置文件的名称

      • application默认的配置文件名称

      • propertySourceLoaders:配置属性加载器。有两种加载器:PropertiesPropertySourceLoaderYamlPropertySourceLoader,分别从properties和xmlyml和yaml两种类型的配置文件中读取配置属性。

        在这里插入图片描述

      该解析器的功能就是根据传入的路径加上配置文件名称,并结合其配置属性加载器。得到确定的配置文件资源。如:传入路径classpath:/,该解析器将返回四个对应的配置文件资源classpath:/application.peropertiesclasspath:/application.xmlclasspath:/application.yamlclasspath:/application.yml

    • loaders属性:配置文件解析器集合

      配置文件解析器通过直接创建ConfigDataLoaders实例完成初始化,在该类的构造方法中,与上面resolvers属性的初始化逻辑相同,也是从META-INF/spring.factories文件中获取配置文件解析器的集合,然后对其进行实例化。

      在这里插入图片描述

      那我们看一下配置文件加载器都有哪些实现类:

      在这里插入图片描述

      看到这里我们应该对resolvers属性和loaders属性之间的关系有个了解了:

      ① resolvers属性用来主要用来解析配置文件所在的目录位置。解析目录获取配置文件

      ② loaders属性用来加载从目录中获取到的配置文件。即从配置文件中加载配置信息

      ③ 两种解析器与两种加载器是存在对应关系的。树形解析器对应树形加载器标准解析器对应标准加载器

    • contributors属性:配置信息贡献者集合,每个贡献者提供不同的信息,可能是已收集的配置信息如环境变量、也可能是配置文件的信息。

      该属性是通过createContributors()方法进行初始化的。该方法将当前环境中的配置属性指定的配置文件路径封装到ConfigDataEnvironmentContributors对象中

      在这里插入图片描述

      由于springboot对ConfigDataEnvironmentContributors的封装和该方法的实现逻辑过于复杂,为了弄清楚该实现,我们将对其进行细化的分析。

      1、首先,第一行代码我们无需过多分析,就是获取当前启动环境中已经收集到的配置信息。如系统属性、环境变量、随机变量。这些在前面分析过了。

      2、然后看DefaultPropertiesPropertySource类的静态方法hasMatchingName()。在遍历配置信息时将其中key为defaultProperties默认属性找出来,对其进行单独处理。

      在这里插入图片描述

      3、ConfigDataEnvironmentContributor类的静态方法ofExisting()

      通过提供静态方法ofExisting()来创建一个ConfigDataEnvironmentContributor类的实例,并且该实例被标记为EXISTING。在此过程中,还会将传入的propertySource对象转为configurationPropertySource对象

      在这里插入图片描述

      在看一下ConfigDataEnvironmentContributor类的构造方法如下

      在这里插入图片描述

      4、通过for循环遍历下来,经过我们的分析,不难发现该循环的目的是通过遍历propertySource数组,将该数组转化为contributor数组

      在这里插入图片描述

      转换过程的示意图如下所示

      在这里插入图片描述

      至此,springboot将最初获取到的配置信息(如:系统属性、命令行参数等)添加到contributor中去了。

      5、将配置文件基本信息添加到contributor中。

      createContributors()方法中,还有一个很重要的逻辑,即将配置文件的基本信息添加到contributor中,配置文件的基本信息其实指的就是配置文件的路径配置。例如我们常用的classpath:/classpath:/config/file:./config/等配置。

      在这里插入图片描述

      springboot对上面的常量定义如下:

      static final String IMPORT_PROPERTY = "spring.config.import";
      private static final ConfigDataLocation[] EMPTY_LOCATIONS = new ConfigDataLocation[0];
      
      static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
      
      static final String LOCATION_PROPERTY = "spring.config.location";
      static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
      static {
          List<ConfigDataLocation> locations = new ArrayList<>();
          locations.add(ConfigDataLocation.of("optional:classpath:/"));
          locations.add(ConfigDataLocation.of("optional:classpath:/config/"));
          locations.add(ConfigDataLocation.of("optional:file:./"));
          locations.add(ConfigDataLocation.of("optional:file:./config/"));
          locations.add(ConfigDataLocation.of("optional:file:./config/*/"));
          DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
      }
      

      在上面截图的方法中,我们看到,getInitialImportContributors()方法调用了三次bindLocations()方法,并将每一次bindLocations()方法的返回值添加到initialContributors集合中。

      我们通过断点查看这三个bindLocations()方法的调用返回给我们什么东西

      第一次调用该方法的结果如下所示。

      在这里插入图片描述

      我们将变量进行替换后,该方法的调用其实是下面这样的

      bindLocations(binder, "spring.config.import", new ConfigDataLocation[0])
      

      所以我们猜测它的目的就是将spring.config.import配置的值转化为ConfigDataLocation类型的数组。而我们的演示没有对其进行配置,所以使用new ConfigDataLocation[0]作为兜底进行返回,得到的结果是一个ConfigDataLocation类型的空数组

      第二次调用该方法的结果如下所示。

      在这里插入图片描述

      我们将变量进行替换后,该方法的调用其实是下面这样的

      bindLocations(binder, "spring.config.additional-location", new ConfigDataLocation[0])
      

      所以我们猜测它的目的就是将spring.config.additional-location配置的值转化为ConfigDataLocation类型的数组。而我们的演示没有对其进行配置,所以使用new ConfigDataLocation[0]作为兜底进行返回,得到的结果同样也是一个ConfigDataLocation类型的空数组

      第三次调用该方法的结果如下所示。

      在这里插入图片描述

      我们将变量进行替换后,该方法的调用其实是下面这样的

      bindLocations(binder, "spring.config.location", DEFAULT_SEARCH_LOCATIONS);
      
      //DEFAULT_SEARCH_LOCATIONS表示默认查找位置,如果没有配置spring.config.location,就使用 DEFAULT_SEARCH_LOCATIONS 作为兜底
      

      通过上面三次对bindLocations()方法的调用,我们得到了三个ConfigDataLocation类型的数组,然后在将这三个数组逐个添加到contributor集合中

      在这里插入图片描述

      前面我们分析过,将系统变量封装为contributor实例时是通过ConfigDataEnvironmentContributor类的静态方法ofExisting()标记EXISTING的,即表示已存在的属性。那么在这里针对配置文件路径的contributor,则是通过另一个静态方法ofInitialImport()标记INITIAL_IMPORT的,即表示初始导入的属性,它只能表示配置文件的位置,我们后面还需要通过该位置去找到对应的配置文件并读取其中的配置。

      所以,又分析了这么大一堆,我们明白了getInitialImportContributors()方法的作用,就是将配置文件的位置转换成对应的contributor集合。如下图所示

      在这里插入图片描述

      再回到createContributors()方法中,该方法将我们根据系统变量转化的contributor集合根据配置文件位置转化的contributor集合进行合并

      在这里插入图片描述

      现在我们得到的contributor集合如下所示

      在这里插入图片描述

      6、在得到contributor集合后,springboot还对该集合进行再次封装,将该集合封装到ConfigDataEnvironmentContributors对象中,并通过rootchildren将其封装成一个树形的结构。

      在这里插入图片描述

      首先我们看到在静态方法of()中,将枚举BEFORE_PROFILE_ACTIVATION作为key,参数contributor集合作为value封装到一个map对象中,然后将该map对象作为children属性传递到ConfigDataEnvironmentContributor()构造方法中,此时得到了一个新的被标记为ROOT的contributor对象中。该对象的结构如下所示

      在这里插入图片描述

      然后将该contributor对象作为root属性保存到ConfigDataEnvironmentContributors对象中,在这里注意区分两个类的区别:

      • ConfigDataEnvironmentContributor:保存配置信息或配置文件位置的contributor类
      • ConfigDataEnvironmentContributors:保存ConfigDataEnvironmentContributor的集合

      此时ConfigDataEnvironmentContributors对象的结构如下所示

      在这里插入图片描述

      综上所述,我们对ConfigDataEnvironment构造方法做一个小总结

      1、binder属性:当前环境中的所有配置信息, 处理配置文件中placeholder解析器

      2、resolvers属性:配置文件位置解析器集合

      3、loaders属性:配置文件解析器集合

      4、contributors属性:保存了profile生效前(BEFORE_PROFILE_ACTIVATION)的属性配置(如:系统属性、环境变量、配置文件位置)。

  • processAndApply()方法

    该方法是从配置文件中读取配置的核心方法,在前面环境后处理器部分中,调用的就是ConfigDataEnvironmentprocessAndApply()方法。

    在这里插入图片描述

    void processAndApply() {
        // 创建配置数据导入器,该导入器中的加载器loaders用来读取配置文件中的数据,然后由导入器将数据保存到contributors中
        ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
                                                             this.loaders);
        
        registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
        
        // 加载默认指定配置目录中的文件名为application的配置文件中的配置信息,保存到当前contributor的children中,并标记为bound_import
        ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
        
        // 创建profile上下文,用来保存当前项目激活的profile
        ConfigDataActivationContext activationContext = createActivationContext(
            contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
        
        // 在不考虑profile的情况下,将第二步未处理的contributor进行处理,同样保存到contributor的children中,并标记为bound_import
        contributors = processWithoutProfiles(contributors, importer, activationContext);
        
        // 确定当前激活的profile,并保存到profile上下文中
        activationContext = withProfiles(contributors, activationContext);
        
        // 根据已确定的profile,加载默认指定配置目录中对应当前profile的配置文件中的配置信息,保存到当前contributor的children中,并标记为bound_import
        contributors = processWithProfiles(contributors, importer, activationContext);
        
        // 将contributor中全部标记为bound_import的配置属性保存到运行环境environment中
        applyToEnvironment(contributors, activationContext);
    }
    

    在该方法中,主要有以下部分逻辑,

    • 从默认指定的配置文件中读取配置信息并保存到contributors中。通过调用processInitial()方法完成。

      默认的配置文件路径前面ConfigDataEnvironment类中已经介绍过,分别是:classpath:/classpath:/config/file:./file:./config/file:./config/*/。以此5个默认的路径、默认的配置文件名称application和默认的配置文件格式properties、xml、yaml、yml为参数,分别获取其对应的配置文件资源。并从中读取配置属性。

      然后将读取到的配置属性封装为contributor对象并标记为BEFORE_PROFILE_ACTIVATION,再将该contributor对象保存到其父contributor对象的children集合中。

      以下图的contributors的结构为例:当处理右边第一个以optional:classpath:/为属性的contributor对象时,我们发现该对象的children属性为空,此时从classpath:/路径下以application命名的配置文件中读取配置并将配置信息封装为contributor对象,再将该对象标记为BEFORE_PROFILE_ACTIVATION并保存到上一层的children属性中。

      由此看出,springboot非常巧妙地利用父子层级的关系来分别表示以配置文件路径为属性的contributor封装着从配置文件中获取的配置信息的contributor

      在这里插入图片描述

    • 初始化环境激活上下文即ConfigDataActivationContext。通过调用createActivationContext()方法完成。

    • 在忽略环境激活上下文的情况下处理当前已收集的contributors。通过调用processWithoutProfiles()方法完成。

    • 确定当前运行环境激活的profile。通过调用withProfiles()方法完成。

      在前面springboot已经把所有位置上以application为名称的配置文件中的配置属性读取并保存到contributors中了。此时只需要再从中获取spring.profiles.active对应的属性,并将获取到的profiles属性保存到环境激活上下文中。

    • 根据已确定的环境激活上下文读取对应的配置信息到contributors中。通过调用processWithProfiles()方法完成。

      再一次根据默认的配置文件路径classpath:/classpath:/config/file:./file:./config/file:./config/*/。以5个默认的路径、默认的配置文件名称application后拼接-和激活的profiles,以及默认的配置文件格式properties、xml、yaml、yml为参数,再次分别获取其对应的配置文件资源。并从中读取配置属性。例如默认配置文件中spring.profiles.active = dev,则此时再次从application-dev.yml中读取配置。

      然后将读取到的配置属性封装为contributor对象并标记为AFTER_PROFILE_ACTIVATION(因为该对象是根据激活的profiles得到的),再将该contributor对象保存到其父contributor对象的children集合中。

      最后再以父子层级的关系将该contributor对象保存到其父级的children属性中。

    • 将contributors中保存的所有配置属性应用到当前运行环境Environment中。

点此进入上一集:springboot创建并配置环境(二) - 配置基础环境

点此进入下一集:springboot创建并配置环境(四) - 配置扩展属性(下集)



纸上得来终觉浅,绝知此事要躬行。

————————我是万万岁,我们下期再见————————

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

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

相关文章

chatglm2外挂知识库问答的简单实现

一、背景 大语言模型应用未来一定是开发热点&#xff0c;现在一个比较成功的应用是外挂知识库。相比chatgpt这个知识库比较庞大&#xff0c;效果比较好的接口。外挂知识库大模型的方式可以在不损失太多效果的条件下获得数据安全。 二、原理 现在比较流行的一个方案是langcha…

OpenLayers入门,OpenLayers使用瓦片加载事件实现瓦片加载进度条,进度条根据瓦片加载数量自动更新进度,加载完毕后隐藏进度条

专栏目录: OpenLayers入门教程汇总目录 前言 本章主要讲解OpenLayers如何使用瓦片加载事件(tileloadstart)、瓦片加载完成事件(tileloadend)以及瓦片加载错误事件(tileloadend)。 并通过OpenLayers使用瓦片加载事件通过实现瓦片加载进度条的案例,实现进度条根据瓦片加…

vue3 vant上传图片

在 Vue 3 中使用 Vant 组件库进行图片上传&#xff0c;您可以使用 Vant 的 ImageUploader 组件。ImageUploader 是 Vant 提供的图片上传组件&#xff0c;可以方便地实现图片上传功能。 以下是一个简单的示例&#xff0c;演示如何在 Vue 3 中使用 Vant 的 ImageUploader 组件进行…

解决Font family [‘sans-serif’] not found问题

序言 以下测试环境都是在 anaconda3 虚拟环境下执行。 激活虚拟环境 conda activate test_python_env 或 source activate test_python_env工具&#xff1a; WinSCP Visual Studio Code 这里笔者使用 WinSCP 工具连接&#xff0c;编辑工具是 Visual Studio Code 一、字体…

【Python数据分析】Python基本数据类型

&#x1f389;欢迎来到Python专栏~Python基本数据类型 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Python学习专栏 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望…

C\C++内存管理

目录 1.C/C内存分布2.C语言中动态内存管理方式3.C中动态内存管理3.1new/delete内置类型3.2new和delete操作自定义类型 4.operator new与operator delete函数4.2重载operator new与operator delete&#xff08;了解&#xff09; 5.new和delete的实现原理5.1内置类型5.2 自定义类…

Vue 3:玩一下web前端技术(六)

前言 本章内容为VUE请求后端技术与相关技术讨论。 上一篇文章地址&#xff1a; Vue 3&#xff1a;玩一下web前端技术&#xff08;五&#xff09;_Lion King的博客-CSDN博客 下一篇文章地址&#xff1a; &#xff08;暂无&#xff09; 一、请求后端技术 1、使用Mock.js模…

【业务功能篇60】Springboot + Spring Security 权限管理 【终篇】

4.4.7 权限校验扩展 4.4.7.1 PreAuthorize注解中的其他方法 hasAuthority&#xff1a;检查调用者是否具有指定的权限&#xff1b; RequestMapping("/hello")PreAuthorize("hasAuthority(system:user:list)")public String hello(){return "hello Sp…

基于BSV的高性能并行CRC硬件电路生成器

01、引 言 循环冗余校验码&#xff0c;即Cyclic Redundancy Check (CRC), 是一种在各种通信系统中广泛应用的检错机制。CRC算法的工作原理和哈希函数类似&#xff0c;具体来说&#xff0c;其对任意长度的数据计算出一段唯一的标识&#xff08;校验和&#xff09;, 然后根据这个…

#typescript 使用file-saver模块#

场景&#xff1a;前端使用file-saver模块做导出文档的时候&#xff0c;出现两个错误 1&#xff1a;npm run build 提示找不到模块&#xff0c;如图 解决方法&#xff1a; 先卸载&#xff0c;不管是否安装都先要卸载 ,然后安装&#xff1a; npm uninstall file-saver npm…

AD21原理图的高级应用(二)层次原理图设计

&#xff08;二&#xff09;层次原理图设计 1.层次原理图概述2.层次化原理图的应用2.1 自上而下的层次化原理图2.2 自下而上的层次化原理图 3.生成层次设计表 对于大规模的电路系统,需要将其按功能分解为若干个电路模块,用户可以单独绘制好各个功能模块,再将它们组合起来继续处…

DevOps-Jenkins

Jenkins Jenkins是一个可扩展的持续集成引擎&#xff0c;是一个开源软件项目&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 官网 应用场景 场景一 研发人员上传开发好的代码到github代码仓库需要将代码下载nginx服务器部署手动下载再…

数据结构:快速的Redis有哪些慢操作?

redis 为什么要这莫快&#xff1f;一个就是他是基于内存的&#xff0c;另外一个就是他是他的数据结构 说到这儿&#xff0c;你肯定会说&#xff1a;“这个我知道&#xff0c;不就是 String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、 Hash&#xff08…

【雕爷学编程】MicroPython动手做(13)——掌控板之RGB三色灯2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

Spring使用注解进行对象装配(DI)

文章目录 一. 什么是对象装配二. 三种注入方式1. 属性注入2. 构造方法注入3. Setter注入 三. 三种注入方式的优缺点四. 综合练习 通过五大类注解可以更便捷的将对象存储到 Spring 中&#xff0c;同样也可以使用注解将已经储存的对象取出来&#xff0c;直接赋值到注解所在类的一…

守护进程——后台服务进程

文章目录 什么是终端进程组会话关系相关函数守护进程创建步骤应用 什么是终端 echo $$:可以查看当前进程的进程号 进程组 会话》进程组》首进程 会话 关系 >&#xff1a;重定向 |&#xff1a;管道 wc -l&#xff1a;查找 &&#xff1a;在后台去运行 SID&#xff1a;会…

小学期笔记——天天酷跑3

画笔的载体是图层 图层的载体是窗体 效果&#xff1a; ------------------- 效果&#xff1a; ---------------------- 实现一个接口可以理解成添加一个能力 接口可以理解为能力的集合 对于abstract&#xff08;判断&#xff1a;没有方法体&#xff09;&#xff0c;尽量使用…

linux系统上安装kail

1.虚拟机安装 加入kail镜像 kail系统的安装 2.更新kail的源 注释原本的源&#xff0c;加入阿里云的源 #阿里云 #deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib #deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib 参考&…

【计算机网络】11、网桥(bridge)、集线器(hub)、交换机(switch)、路由器(router)、网关(gateway)

文章目录 一、网桥&#xff08;bridge)二、集线器&#xff08;hub&#xff09;三、交换机&#xff08;switch)四、路由器&#xff08;router&#xff09;五、网关&#xff08;gateway&#xff09; 对于hub&#xff0c;一个包过来后&#xff0c;直接将包转发到其他口。 对于桥&…

【C++ 程序设计】实战:C++ 变量实践练习题

目录 01. 变量&#xff1a;定义 02. 变量&#xff1a;初始化 03. 变量&#xff1a;参数传递 04. 变量&#xff1a;格式说明符 ① 占位符 “%d” 改为格式说明符 “%llu” ② 占位符 “%d” 改为格式说明符 “%f” 或 “%e” 05. 变量&#xff1a;字节数统计 06. 变量&a…