SpringBoot多环境配置的实现

前言

开发过程中必然使用到的多环境案例,通过简单的案例分析多环境配置的实现过程。

一、案例

1.1主配置文件

spring:profiles:active: prod
server:port: 8080

1.2多环境配置文件

  • 开发环境
blog:domain: http://localhost:8080
  • 测试环境
blog:domain: https://test.lazysnailstudio.com
  • 生产环境
blog:domain: https://lazysnailstudio.com

1.3测试源码

package com.lazy.snail.service;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;/*** @ClassName BlogInfoService* @Description TODO* @Author lazysnail* @Date 2024/11/15 14:30* @Version 1.0*/
@Service
public class BlogInfoService {@Value("${blog.domain}")private String domain;public String getDomain() {return domain;}
}
package com.lazy.snail.service;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;/*** @ClassName BlogInfoService* @Description TODO* @Author lazysnail* @Date 2024/11/15 14:30* @Version 1.0*/
@Service
public class BlogInfoService {@Value("${blog.domain}")private String domain;public String getDomain() {return domain;}
}

1.4测试结果

  • 开发环境

image-20241117213950534

  • 测试环境

image-20241117214023941

  • 生产环境

image-20241117214142326

二、配置文件解析过程

2.1SpringBoot启动过程,环境准备阶段

// SpringApplication
public ConfigurableApplicationContext run(String... args) {ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
}private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {listeners.environmentPrepared(bootstrapContext, environment);
}

2.2事件处理

  • 应用环境准备事件:ApplicationEnvironmentPreparedEvent

  • 事件监听(监听器:EnvironmentPostProcessorApplicationListener)

// EnvironmentPostProcessorApplicationListener
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {ConfigurableEnvironment environment = event.getEnvironment();SpringApplication application = event.getSpringApplication();for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),event.getBootstrapContext())) {postProcessor.postProcessEnvironment(environment, application);}
}
  • 遍历环境后置处理器

image-20241117215028418

2.3配置数据环境后置处理

  • 核心方法processAndApply
// ConfigDataEnvironmentPostProcessor
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
}void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,Collection<String> additionalProfiles) {try {this.logger.trace("Post-processing environment to add config data");resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();}catch (UseLegacyConfigProcessingException ex) {this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",ex.getConfigurationProperty()));configureAdditionalProfiles(environment, additionalProfiles);postProcessUsingLegacyApplicationListener(environment, resourceLoader);}
}
  • processInitial方法解析和加载初始配置文件(如application.yml或application.properties)的内容,封装为contributors对象,解析出来的配置没有立即应用到Spring的Environment中。
  • processWithoutProfiles在基础的多环境中基本没有额外操作。
  • withProfiles主要是确定激活的profile
  • processWithProfiles处理带有profile的配置
  • applyToEnvironment将配置信息应用到Spring的环境中
// ConfigDataEnvironment
void processAndApply() {ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,this.loaders);registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);ConfigDataActivationContext activationContext = createActivationContext(contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));contributors = processWithoutProfiles(contributors, importer, activationContext);activationContext = withProfiles(contributors, activationContext);contributors = processWithProfiles(contributors, importer, activationContext);applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),importer.getOptionalLocations());
}private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,ConfigDataImporter importer) {this.logger.trace("Processing initial config data environment contributors without activation context");contributors = contributors.withProcessedImports(importer, null);registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);return contributors;
}

2.4配置文件路径搜索

找到需要处理的导入,加载相关配置,将结果合并到当前的配置贡献者集合(ConfigDataEnvironmentContributors)中。

// ConfigDataEnvironmentContributors
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,ConfigDataActivationContext activationContext) {// BEFORE_PROFILE_ACTIVATION、AFTER_PROFILE_ACTIVATIONImportPhase importPhase = ImportPhase.get(activationContext);this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,(activationContext != null) ? activationContext : "no activation context"));ConfigDataEnvironmentContributors result = this;int processed = 0;while (true) {ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);if (contributor == null) {this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));return result;}if (contributor.getKind() == Kind.UNBOUND_IMPORT) {ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(result, activationContext);result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,result.getRoot().withReplacement(contributor, bound));continue;}ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(result, contributor, activationContext);ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);List<ConfigDataLocation> imports = contributor.getImports();this.logger.trace(LogMessage.format("Processing imports %s", imports));Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,locationResolverContext, loaderContext, imports);this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,asContributors(imported));result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,result.getRoot().withReplacement(contributor, contributorAndChildren));processed++;}
}

指定配置文件的搜索路径表达式

optional:file:./;optional:file:./config/;optional:file:./config/*/

image-20241117223025218

classpath:/;optional:classpath:/config/

image-20241117223149610

2.5配置文件解析加载

// ConfigDataImporter
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,List<ConfigDataLocation> locations) {try {Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);return load(loaderContext, resolved);} catch (IOException ex) {throw new IllegalStateException("IO error on loading imports from " + locations, ex);}
}

两个解析器

image-20241117223422419

特性ConfigTreeConfigDataLocationResolverStandardConfigDataLocationResolver
主要用途解析配置树格式文件(文件名-文件内容映射)。解析传统配置文件(.properties.yml)。
典型场景容器化环境,如 Kubernetes ConfigMap 或 Secrets。通常的文件或类路径中的配置文件。
数据来源挂载的目录结构,例如 /etc/config本地文件系统或类路径,例如 application.properties
配置导入方式spring.config.import=configtree:/path/to/config/默认加载机制或 spring.config.import=file:/path/to/file/

2.5.1解析主配置文件

image-20241117223953518

  • 加载配置文件
// StandardConfigDataLoader
public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource)throws IOException, ConfigDataNotFoundException {if (resource.isEmptyDirectory()) {return ConfigData.EMPTY;}ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, resource.getResource());StandardConfigDataReference reference = resource.getReference();Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(),Origin.from(reference.getConfigDataLocation()));String name = String.format("Config resource '%s' via location '%s'", resource,reference.getConfigDataLocation());List<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;return new ConfigData(propertySources, options);
}
  • 选择对应的加载器加载文件

image-20241117224512235

2.5.2解析激活环境配置

  • 获取激活环境
// ConfigDataEnvironment
private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributors contributors,ConfigDataActivationContext activationContext) {this.logger.trace("Deducing profiles from current config data environment contributors");Binder binder = contributors.getBinder(activationContext,(contributor) -> !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES),BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);try {Set<String> additionalProfiles = new LinkedHashSet<>(this.additionalProfiles);additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext));// 构造方法中获取应该激活的环境Profiles profiles = new Profiles(this.environment, binder, additionalProfiles);return activationContext.withProfiles(profiles);} catch (BindException ex) {if (ex.getCause() instanceof InactiveConfigDataAccessException) {throw (InactiveConfigDataAccessException) ex.getCause();}throw ex;}
}

image-20241117230715095

  • 处理激活环境中的配置信息

image-20241117230817184

  • 调用withProcessedImports对application-profiles.yml进行解析加载

image-20241117231313192

2.6环境应用

  • 将所有解析的配置信息应用到Spring的环境中
// ConfigDataEnvironment
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,Set<ConfigDataLocation> optionalLocations) {checkForInvalidProperties(contributors);checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);MutablePropertySources propertySources = this.environment.getPropertySources();applyContributor(contributors, activationContext, propertySources);DefaultPropertiesPropertySource.moveToEnd(propertySources);Profiles profiles = activationContext.getProfiles();this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));this.environmentUpdateListener.onSetProfiles(profiles);
}

image-20241117231718974

三、总结

3.1实现的底层流程

(1)processInitial阶段

  • 首先加载默认配置文件application.yml。
  • 如果配置中存在动态导入 (spring.config.import),会解析导入源,但此时不会解析 spring.profiles.active。

(2)processWithoutProfiles阶段

  • 执行额外的静态配置绑定,如处理动态导入的配置源。
  • 此阶段仍未激活Profiles,仅为后续处理提供基础环境。

(3)withProfiles阶段

  • 确定当前激活的Profile:
    • 根据spring.profiles.active获取激活的Profiles。
    • 如果没有设置,则使用spring.profiles.default或回退到默认Profile。
  • 动态调整配置上下文,为接下来的加载提供Profile信息。

(4)processWithProfiles阶段

  • 基于激活的Profiles,加载对应的配置文件(如application-dev.yml)。
  • 合并所有配置源,按优先级覆盖默认配置。

(5)applyToEnvironment阶段

  • 将解析后的所有配置应用到Spring的Environment对象中。
  • Spring的容器在运行时可以直接从Environment中读取合并后的配置值。

3.2实现机制

配置文件分层:支持默认和环境特定配置文件。

动态激活:通过spring.profiles.active指定激活的环境。

加载优先级:先加载默认配置,再加载特定环境配置,按优先级覆盖。

合并与应用:所有配置合并后统一注入到Environment,供应用运行时使用。

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

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

相关文章

另外一种缓冲式图片组件的用法

文章目录 1. 概念介绍2. 使用方法2.1 基本用法2.2 缓冲原理3. 示例代码4. 内容总结我们在上一章回中介绍了"FadeInImage组件"相关的内容,本章回中将介绍CachedNetworkImage组件.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的CachedNetwo…

Linux下多线程

在Linux下的底层里并没有多线程这个概念&#xff0c;取而代之的是轻量级进程的概念。应为在Llinu下内核下并没有TCB,而只有PCB。 线程是什么 在⼀个程序⾥的⼀个执⾏路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“⼀个进程内部 的控制序…

Win10/11 安装使用 Neo4j Community Edition

如果你下载的是 Neo4j Community Edition 的压缩包&#xff0c;意味着你需要手动解压并配置 Neo4j。以下是详细的使用步骤&#xff1a; 0. 下载压缩包 访问Neo4j官网&#xff0c;找到 Community Edition 版本并选择 4.x 或者 5.x 下载&#xff1a;https://neo4j.com/deployme…

PCB+SMT线上报价系统+PCB生产ERP系统自动化拼板模块升级

PCB生产ERP系统的智能拼版技术&#xff0c;是基于PCB前端报价系统获取到的用户或市场人员已录入系统的板子尺寸及set参数等&#xff0c;按照最优原则或利用率最大化原则自动进行计算并输出拼版样式图和板材利用率&#xff0c;提高工程人员效率&#xff0c;减少板材的浪费。覆铜…

Excel根据条件动态索引单元格范围

假如我是一个老板&#xff0c;下面有数不胜数的员工&#xff0c;我要检查他们每周的工作产出&#xff0c;列一个排行榜&#xff0c;提高员工积极性&#xff0c;毕竟多劳多得嘛。 每天去手动统计&#xff0c;未免显得不太聪明&#xff0c;我们可以利用公式来解决这个问题。 我们…

SpringBoot配置相关的内容

依赖Starter和Parent 查依赖坐标网站&#xff1a;Maven Repository: Search/Browse/Explorehttps://mvnrepository.com/ 设置配置文件 配置文件相关的配置 yml多个数据的书写 配置文件的读取

机器学习-37-对ML的思考之机器学习发展的三个阶段和驱动AI发展三驾马车的由来

文章目录 1 引言2 机器学习发展的三个阶段2.1 萌芽期(20世纪50年代)2.1.1 达特茅斯会议(人工智能诞生)2.1.2 机器学习名称的由来2.2 知识期(20世纪80年代)2.2.1 知识瓶颈问题2.2.2 机器学习顶级会议ICML2.2.3 Machine Learning创刊2.2.4 神经网络规则抽取2.3 算法期(20世纪90年…

使用win32com将ppt(x)文件转换为pdf文件

本文来记录下如何使用win32com将ppt(x)文件转换为pdf文件 文章目录 win32com概述win32com优缺点代码实例本文小结 win32com概述 Pywin32 是一个用于与 Microsoft Windows 操作系统交互的 Python 扩展模块&#xff0c;它提供了对多个 Windows API 的访问&#xff0c;包括对 Mic…

鸿蒙实战:页面跳转

文章目录 1. 实战概述2. 实现步骤2.1 创建项目2.2 准备图片素材2.3 编写首页代码2.4 创建第二个页面 3. 测试效果4. 实战总结 1. 实战概述 实战概述&#xff1a;本实战通过ArkUI框架&#xff0c;在鸿蒙系统上开发了一个简单的两页面应用。首页显示问候语和“下一页”按钮&…

uniapp微信小程序接入airkiss插件进行WIFI配网

本文可参考uniapp小程序插件 一.申请插件 微信公众平台设置页链接&#xff1a;微信公众平台 登录您的小程序微信公众平台&#xff0c;进入设置页&#xff0c;在第三方设置->插件管理->添加插件中申请AiThinkerAirkissforWXMini插件&#xff0c;申请的插件appId为【wx6…

django解决跨域问题

django解决跨域问题 第一步 查看自己的 django 依赖里面有没有 django-cors-headers 包 直接 cmd pin list第二步如果没有 在自己的 pycharm 里面安装 django-cors-headers 包 pip install django-cors-headers第三步检查是否安装成功 查看自己的 django-cors-headers 安…

【51单片机】LCD1602液晶显示屏

学习使用的开发板&#xff1a;STC89C52RC/LE52RC 编程软件&#xff1a;Keil5 烧录软件&#xff1a;stc-isp 开发板实图&#xff1a; 文章目录 LCD1602存储结构时序结构 编码 —— 显示字符、数字 LCD1602 LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶显示屏是…

【C++派生类新增对象的初始化顺序】单继承下派生类新增成员对象的初始化顺序

单继承下派生类新增成员对象的初始化顺序 &#xff08;1&#xff09;【意识】派生类新增成员对象也要初始化&#xff0c;千万别忘&#xff01; &#xff08;2&#xff09;派生类构造函数执行顺序 ①调用基类构造函数 ②对派生类的新增成员对象初始化[调用顺序为类中声明顺序] ③…

红外遥控信号解码

红外遥控信号解码 之前就已经做过红外遥控的解码了&#xff0c;但是一直没有做记录&#xff0c;最近的项目又使用到了红外遥控&#xff0c;索性就把他捡起来记录一下&#xff0c;对于信号的解码&#xff0c;我一般的习惯都是先用逻辑分析仪抓取一下信号波形&#xff0c;然后对…

基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络

一、介绍 垃圾识别分类系统。本系统采用Python作为主要编程语言&#xff0c;通过收集了5种常见的垃圾数据集&#xff08;‘塑料’, ‘玻璃’, ‘纸张’, ‘纸板’, ‘金属’&#xff09;&#xff0c;然后基于TensorFlow搭建卷积神经网络算法模型&#xff0c;通过对图像数据集进…

stdin文件流指针

stdin文件流指针&#xff08;FILE *&#xff09;&#xff0c;用于表示标准输入流。它通常与键盘进行交互&#xff0c;也可以通过重定向将其他输入源作为标准输入。

GPU分布式通信技术-PCle、NVLink、NVSwitch深度解析

GPU分布式通信技术-PCle、NVLink、NVSwitch 大模型时代已到来&#xff0c;成为AI核心驱动力。然而&#xff0c;训练大模型却面临巨大挑战&#xff1a;庞大的GPU资源需求和漫长的学习过程。 要实现跨多个 GPU 的模型训练&#xff0c;需要使用分布式通信和 NVLink。此外&#xf…

调用门提权

在我写的2.保护模式&#xff0b;段探测这篇文章中&#xff0c;我们提到了S位对于段描述符的控制&#xff0c;之前我们已经介绍了代码段和数据段&#xff0c;现在我们来把目光转到系统段 在这么多中结构里面&#xff0c;我们今天要介绍的就是编号为12的&#xff0c;32位调用门 结…

文心一言编写小球反弹程序并优化

使用文心一言尝试编写一个小游戏&#xff0c;先完成 1.python中用pygame模块设计出一个显示区域720x540的屏幕&#xff0c;并绘制一个小球&#xff0c;可以完成小球在显示区域内自动随机直线移动&#xff0c;碰到显示区域的便捷并反弹 import pygame import random import sy…

华为开源自研AI框架昇思MindSpore应用案例:人体关键点检测模型Lite-HRNet

如果你对MindSpore感兴趣&#xff0c;可以关注昇思MindSpore社区 一、环境准备 1.进入ModelArts官网 云平台帮助用户快速创建和部署模型&#xff0c;管理全周期AI工作流&#xff0c;选择下面的云平台以开始使用昇思MindSpore&#xff0c;获取安装命令&#xff0c;安装MindSpo…