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

文章目录

  • 一、介绍
  • 二、配置文件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_importConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);// 创建profile上下文,用来保存当前项目激活的profileConfigDataActivationContext activationContext = createActivationContext(contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));// 在不考虑profile的情况下,将第二步未处理的contributor进行处理,同样保存到contributor的children中,并标记为bound_importcontributors = processWithoutProfiles(contributors, importer, activationContext);// 确定当前激活的profile,并保存到profile上下文中activationContext = withProfiles(contributors, activationContext);// 根据已确定的profile,加载默认指定配置目录中对应当前profile的配置文件中的配置信息,保存到当前contributor的children中,并标记为bound_importcontributors = 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.mzph.cn/news/12453.shtml

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

相关文章

【Chat GPT】用 ChatGPT 运行 Python

前言 ChatGPT 是一个基于 GPT-2 模型的人工智能聊天机器人&#xff0c;它可以进行智能对话&#xff0c;同时还支持 Python 编程语言的运行&#xff0c;可以通过 API 接口进行调用。本文将介绍如何使用 ChatGPT 运行 Python 代码&#xff0c;并提供一个实际代码案例。 ChatGPT …

简单理解大模型参数高效微调中的LoRA(Low-Rank Adaptation)

[论文地址] [代码] [ICLR 22] 阅前须知&#xff1a;本博文可能有描述不准确/过度简化/出错的地方&#xff0c;仅供参考。 网络结构 其中&#xff0c;原有模型的参数是直接冻结的&#xff0c;可训练参数只有额外引入的LoRA参数(由nn.Parameter实现)。 模型微调的本质 记网络原…

LabVIEW实现三相异步电机磁通模型

LabVIEW实现三相异步电机磁通模型 三相异步电动机由于经济和出色的机电坚固性而广泛用于工业化应用。这台机器的设计和驱动非常简单&#xff0c;但在控制扭矩和速度方面&#xff0c;它隐藏了相当大的功能复杂性。通过数学建模&#xff0c;可以理解机器动力学。 基于微分方程的…

【嵌入式学习笔记】嵌入式基础9——STM32启动过程

1.MAP文件浅析 1.1.MDK编译后生成的中间过程文件 1.2.Map文件构成&#xff1a; 程序段交叉引用关系&#xff08;Section Cross References&#xff09;&#xff1a;描述各文件之间函数调用关系删除映像未使用的程序段&#xff08;Removing Unused input sections from the im…

【图像处理】使用 OpenCV 将您的照片变成卡通

图像到卡通 一、说明 在当今世界&#xff0c;我们被图像和视频所包围。从社交媒体到广告&#xff0c;图像已成为一种强大的交流媒介。但是你有没有想过&#xff0c;如果你能把你的照片变成卡通会发生什么&#xff1f;想象一下&#xff0c;为您最喜欢的照片创建动画版本&#xf…

Pytorch深度学习-----神经网络的基本骨架-nn.Module的使用

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

python爬虫基础入门——利用requests和BeautifulSoup

(本文是自己学习爬虫的一点笔记和感悟) 经过python的初步学习,对字符串、列表、字典、元祖、条件语句、循环语句……等概念应该已经有了整体印象,终于可以着手做一些小练习来巩固知识点,写爬虫练习再适合不过。 1. 网页基础 爬虫的本质就是从网页中获取所需的信息,对网…

类加载机制与类加载器

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ Java 源码是如何形成类文件的&#xff0c;类文件又是如何加载到虚拟机的&#xff0c;类加载有哪些机制和原则呢&#xff1f;本文将为大家一一介绍。 1 Java 源码形成类文件…

基于拉格朗日-遗传算法的最优分布式能源DG选址与定容(Matlab代码实现)

目录 1 概述 2 数学模型 2.1 问题表述 2.2 DG的最佳位置和容量&#xff08;解析法&#xff09; 2.3 使用 GA 进行最佳功率因数确定和 DG 分配 3 仿真结果与讨论 3.1 33 节点测试配电系统的仿真 3.2 69 节点测试配电系统仿真 4 结论 1 概述 为了使系统网损达到最低值&a…

AI Chat 设计模式:10. 组合模式

本文是该系列的第八篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 给我介绍一下组合模式A.1Q.2 好的&#xff0c;给我举一个组合模式的例子&#xff0c;使…

idea导入maven项目问题

问题产生原因&#xff1a; ①idea加载maven项目&#xff0c;如果网络不通畅&#xff0c;会在maven仓库中产生一个文件&#xff0c;如下图所示: ②当网络通畅时&#xff0c;在下载就会因为此文件导致无法下载正确的maven依赖 解决方案&#xff1a; ①打开maven仓库的根目录 ②…

ts中声明引入未使用的报错——解决方案

在编写ts项目的时候&#xff0c;经常会出现如下报错&#xff1a; 导入声明中的所有导入都未使用 这是因为导入的模块暂时没有使用&#xff0c;ts给的一个提示信息 解决方案&#xff1a; 在ts.config.json中 把noUnusedLocals 设置为false即可 {"compilerOptions"…

【雕爷学编程】Arduino动手做(175)---机智云ESP8266开发板模块4

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

【UE5 多人联机教程】03-创建游戏

效果 步骤 打开“UMG_MainMenu”&#xff0c;增加创建房间按钮的点击事件 添加如下节点 其中&#xff0c;“FUNL Fast Create Widget”是插件自带的函数节点&#xff0c;内容如下&#xff1a; “创建会话”节点指游戏成功创建一个会话后&#xff0c;游戏的其他实例即可发现&am…

微服务体系<1>

我们的微服务架构 我们的微服务架构和单体架构的区别 什么是微服务架构 微服务就是吧我们传统的单体服务分成 订单模块 库存模块 账户模块单体模块 是本地调用 从订单模块 调用到库存模块 再到账户模块 这三个模块都是调用的同一个数据库 这就是我们的单体架构微服务 就是…

Training-Time-Friendly Network for Real-Time Object Detection 论文学习

1. 解决了什么问题&#xff1f; 目前的目标检测器很少能做到快速训练、快速推理&#xff0c;并同时保持准确率。直觉上&#xff0c;推理越快的检测器应该训练也很快&#xff0c;但大多数的实时检测器反而需要更长的训练时间。准确率高的检测器大致可分为两类&#xff1a;推理时…

银河麒麟安装mysql数据库(mariadb)-银河麒麟安装JDK-银河麒麟安装nginx(附安装包)

银河麒麟离线全套安装教程&#xff08;手把手教程&#xff09; 1.银河麒麟服务器系统安装mysql数据库&#xff08;mariadb&#xff09; 2.银河麒麟桌面系统安装mysql数据库&#xff08;mariadb&#xff09; 3.银河麒麟服务器系统安装JDK 4.银河麒麟桌面系统安装JDK 5.银河麒麟…

青大数据结构【2021】

一、单选&#xff08;17&#xff01;&#xff09; 根据中序遍历得到降序序列可以知道&#xff0c;每个结点的左子树的结点的值比该结点的值小&#xff0c;因为没有重复的关键字&#xff0c;所以拥有最大值的结点没有左子树。 二、简答 三、分析计算 四、算法分析 3.迪杰斯特拉…

windows安装Elasticsearch8.9.0

官网解压安装好路径&#xff08;非中文&#xff0c;无空格&#xff09; 可参考 言之有李LAX csdn http://t.csdn.cn/S2oju本人使用jdk17 修改配置elasticsearch.yml xpack.security.enabled: false xpack.security.http.ssl:enabled: false直接点击bin\elasticsearch.bat…

Unity 工具之 NuGetForUnity 包管理器,方便在 Unity 中的进行包管理的简单使用

Unity 工具之 NuGetForUnity 包管理器&#xff0c;方便在 Unity 中的进行包管理的简单使用 目录 Unity 工具之 NuGetForUnity 包管理器&#xff0c;方便在 Unity 中的进行包管理的简单使用 一、简单介绍 二、NuGetForUnity 的下载导入 Unity 三、NuGetForUnity 在 Unity 的…