springboot如何创建并配置环境3 - 配置扩展属性(上)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: springboot如何创建并配置环境3 - 配置扩展属性(上)

[toc]

一、介绍

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

本文基于以下版本进行展开:

  • jdk:1.8
  • springboot:2.4.3

另外:由于篇幅过长,决定分四集文章来讲解分析

一、创建环境

二、配置基础环境

三、配置扩展属性 - 上

四、配置扩展属性 - 下

二、配置文件application.yml

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

配置文件1.png

然后进入断点调试。

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

还没有加载配置文件.png

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

已经加载配置文件.png

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

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

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

spring运行监听器的environmentPrepared()方法.png

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

RunListener集合.png

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

getRunListeners()方法的调用.png

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

getRunListeners()方法.png

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

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

发布事件的RunListener.png

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

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

广播环境准备就绪事件.png

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

针对环境准备就绪事件的监听器.png

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

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

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

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

环境后处理监听器对事件的处理方法.png

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

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

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

getEnvironmentPostProcessors()方法.png

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

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

环境后处理器的spring应用监听器.png

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

从spring.factories文件中获取环境后处理器.png

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

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

获取到的环境后处理器集合.png

由此可见,对启动环境的处理可不止是从配置文件中获取配置这么简单,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()方法实现

ConfigData环境后处理器的postProcessEnvironment()方法.png

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

其实就只有三件事:

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

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

ConfigDataEnvironment的配置文件地址常量.png

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

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

  • 构造函数

    ConfigDataEnvironment构造器.png

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

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

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

    实例化Binder对象.png

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

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

配置文件中的placeholder.png

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

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

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

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

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

    实例化配置文件位置解析器的过程.png

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

实例化的配置文件位置解析器.png

这里我们简单介绍一下标准配置文件解析器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两种类型的配置文件中读取配置属性。

处理properties或yml类型文件的配置属性加载器.png

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

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

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

    初始化配置文件加载器的过程.png

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

实例化的配置文件加载器集合.png

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

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

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

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

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

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

    封装配置属性和配置文件路径集合.png

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

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

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

hasMatchingName()方法判断当前配置信息的key为defaultProperties.png

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

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

将propertySource转为标记为existing类型的contributor对象.png

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

被标记为existing的contributor实例.png

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

将propertySource数组转化为contributor数组的for循环.png

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

将propertySource数组转化为contributor数组的过程.png

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

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

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

getInitialImportConttributors()方法.png

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()方法调用1.png

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

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

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

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

bindLocations()方法调用2.png

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

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

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

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

bindLocations()方法调用3.png

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

    bindLocations(binder, "spring.config.location", DEFAULT_SEARCH_LOCATIONS);

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

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

将配置文件路径封装到contributor并标记为initail_import.png

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

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

配置文件位置的contributor集合.png

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

createContributors()方法合并两种contributor集合.png

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

合并后的contributor集合.png

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

将contributor集合封装到树形结构中.png

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

contributor集合作为children属性,并被标记为ROOT的contributor对象.png

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

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

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

contributors结构.png

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

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

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

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

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

  • processAndApply()方法

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

processAndApply()方法.png

  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

contributors结构.png

  • 初始化环境激活上下文即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中。

点此进入上一集:配置基础环境

点此进入下一集:配置扩展属性(下)




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

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

相关文章
|
11天前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
30 0
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
38 0
|
15天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
27 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
5天前
|
存储 前端开发 JavaScript
springboot中路径默认配置与重定向/转发所存在的域对象
Spring Boot 提供了简便的路径默认配置和强大的重定向/转发机制,通过合理使用这些功能,可以实现灵活的请求处理和数据传递。理解并掌握不同域对象的生命周期和使用场景,是构建高效、健壮 Web 应用的关键。通过上述详细介绍和示例,相信读者能够更好地应用这些知识,优化自己的 Spring Boot 应用。
15 3
|
14天前
|
Java 数据库连接
SpringBoot配置多数据源实战
第四届光学与机器视觉国际学术会议(ICOMV 2025) 2025 4th International Conference on Optics and Machine Vision
43 8
|
11天前
|
Java 数据库连接 数据库
springboot启动配置文件-bootstrap.yml常用基本配置
以上是一些常用的基本配置项,在实际应用中可能会根据需求有所变化。通过合理配置 `bootstrap.yml`文件,可以确保应用程序在启动阶段加载正确的配置,并顺利启动运行。
22 2
|
23天前
|
Java Spring 容器
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
46 3
|
25天前
|
Java 开发者 Spring
精通SpringBoot:16个扩展接口精讲
【10月更文挑战第16天】 SpringBoot以其简化的配置和强大的扩展性,成为了Java开发者的首选框架之一。SpringBoot提供了一系列的扩展接口,使得开发者能够灵活地定制和扩展应用的行为。掌握这些扩展接口,能够帮助我们写出更加优雅和高效的代码。本文将详细介绍16个SpringBoot的扩展接口,并探讨它们在实际开发中的应用。
41 1
|
1月前
|
druid Java Maven
|
29天前
|
监控 Java 开发者
掌握SpringBoot扩展接口:提升代码优雅度的16个技巧
【10月更文挑战第20天】 SpringBoot以其简化配置和快速开发而受到开发者的青睐。除了基本的CRUD操作外,SpringBoot还提供了丰富的扩展接口,让我们能够更灵活地定制和扩展应用。以下是16个常用的SpringBoot扩展接口,掌握它们将帮助你写出更加优雅的代码。
49 0
下一篇
无影云桌面