SpringBoot多环境配置的实现

news2024/11/19 4:19:33

前言

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

一、案例

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_ACTIVATION
    ImportPhase 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.coloradmin.cn/o/2243152.html

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

相关文章

鸿蒙HarmonyOS 地图定位到当前位置 site查询等操作

应用服务Map使用 地图定位 地点查询及导航 周边查询 点位标记定义等 地图定位 前提地图已经能正常显示&#xff0c;若不能显示请大家参考之前的那篇如何显示地图的博文 地图相关的api 位置效果图&#xff1a; module.json5配置权限 "requestPermissions": [{&…

AntFlow 0.11.0版发布,增加springboot starter模块,一款设计上借鉴钉钉工作流的免费企业级审批流平台

AntFlow 0.11.0版发布,增加springboot starter模块,一款设计上借鉴钉钉工作流的免费企业级审批流平台 传统老牌工作流引擎比如activiti,flowable或者camunda等虽然功能强大&#xff0c;也被企业广泛采用&#xff0c;然后也存着在诸如学习曲线陡峭&#xff0c;上手难度大&#x…

Timeline动画「硬切」的问题

1&#xff09;Timeline动画「硬切」的问题 2&#xff09;移动平台纹理压缩格式选择ASTC&#xff0c;美术出图还需遵守POT吗 3&#xff09;如何去掉DOTS Unity.Entities.Graphics创建的BatchRendererGroup的UI相机回调 4&#xff09;Timeline播放动画会产生位移的问题 这是第409…

《设计模式》创建型模式总结

目录 创建型模式概述 Factory Method: 唯一的类创建型模式 Abstract Factory Builder模式 Prototype模式 Singleton模式 最近在参与一个量化交易系统的项目&#xff0c;里面涉及到用java来重构部分vnpy的开源框架&#xff0c;因为是框架的搭建&#xff0c;所以会涉及到像…

【论文阅读】主动推理:作为感知行为的理论

文章目录 主动推理&#xff1a;作为感知行为的理论摘要1.引言2. 主动推理的概念和历史根源3. 主动推理的规范视角—以及它的发展历程 未完待续 主动推理&#xff1a;作为感知行为的理论 Active inference as a theory of sentient behavior 摘要 这篇文章综述了主动推理的历…

React--》如何高效管理前端环境变量:开发与生产环境配置详解

在前端开发中&#xff0c;如何让项目在不同环境下表现得更为灵活与高效&#xff0c;是每个开发者必须面对的挑战&#xff0c;从开发阶段的调试到生产环境的优化&#xff0c;环境变量配置无疑是其中的关键。 env配置文件&#xff1a;通常用于管理项目的环境变量&#xff0c;环境…

【工具插件类教学】在 Unity 中使用 iTextSharp 实现 PDF 文件生成与导出

目录 一、准备工作 1. 安装 iTextSharp 2. 准备资源文件 二、创建 ExportPDFTool 脚本 1、初始化 PDF 文件,设置字体 2、添加标题、内容、表格和图片 三、使用工具类生成 PDF 四、源码地址 在 Unity 项目中,我们有时会需要生成带有文本、表格和图片的 PDF 文件,以便…

【AlphaFold3】开源本地的安装及使用

文章目录 安装安装DockerInstalling Docker on Host启用Rootless Docker 安装 GPU 支持安装 NVIDIA 驱动程序安装 NVIDIA 对 Docker 的支持 获取 AlphaFold 3 源代码获取基因数据库获取模型参数构建将运行 AlphaFold 3 的 Docker 容器 参考 AlphaFold3: https://github.com/goo…

[JAVA]MyBatis框架—获取SqlSession对象

SqlSessionFactory作为MyBatis框架的核心接口有三大特性 SqlSessionFactory是MyBatis的核心对象 用于初始化MyBatis&#xff0c;创建SqlSession对象 保证SqlSessionFactory在应用中全局唯一 1.SqlSessionFactory是MyBatis的核心对象 假设我们要查询数据库的用户信息&#x…

ArkTS学习笔记:ArkTS起步

ArkTS是HarmonyOS的主力应用开发语言&#xff0c;基于TypeScript扩展&#xff0c;强化了静态检查和分析&#xff0c;旨在提升程序稳定性和性能。它采用静态类型&#xff0c;禁止运行时改变对象布局&#xff0c;并对UI开发框架能力进行扩展&#xff0c;支持声明式UI描述和自定义…

JAVA 之 JDBC

JDBC概述 基本介绍 1.JDBC为访问不同的数据库提供了统一的接口&#xff0c;为使用者屏蔽了细节问题。 2.Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统&#xff0c;从而完成对数据库的各种操作。 3.JDBC的基本原理[ 重要 ] 4.模拟JDBC com.lmbc.myjdbc…

用 Python 从零开始创建神经网络(五):损失函数(Loss Functions)计算网络误差

用损失函数&#xff08;Loss Functions&#xff09;计算网络误差 引言1. 分类交叉熵损失&#xff08;Categorical Cross-Entropy Loss&#xff09;2. 分类交叉熵损失类&#xff08;The Categorical Cross-Entropy Loss Class&#xff09;展示到目前为止的所有代码3. 准确率计算…

Redis做分布式锁

&#xff08;一&#xff09;为什么要有分布式锁以及本质 在一个分布式的系统中&#xff0c;会涉及到多个客户端访问同一个公共资源的问题&#xff0c;这时候我们就需要通过锁来做互斥控制&#xff0c;来避免类似于线程安全的问题 因为我们学过的sychronized只能对线程加锁&…

阿里云引领智算集群网络架构的新一轮变革

阿里云引领智算集群网络架构的新一轮变革 云布道师 11 月 8 日~ 10 日在江苏张家港召开的 CCF ChinaNet&#xff08;即中国网络大会&#xff09;上&#xff0c;众多院士、教授和业界技术领袖齐聚一堂&#xff0c;畅谈网络未来的发展方向&#xff0c;聚焦智算集群网络的创新变…

预处理(1)(手绘)

大家好&#xff0c;今天给大家分享一下编译器预处理阶段&#xff0c;那么我们来看看。 上面是一些预处理阶段的知识&#xff0c;那么明天给大家讲讲宏吧。 今天分享就到这里&#xff0c;谢谢大家&#xff01;&#xff01;

ZYNQ程序固化——ZYNQ学习笔记7

一、ZYNQ启动过程 二、 SD卡启动实操 1、对ZYNQ进行配置添加Flash 2、添加SD卡 3、重新生成硬件信息 4、创建vitis工程文件 5、勾选板级支持包 6、对系统工程进行整体编译&#xff0c;生成两个Debug文件&#xff0c;如图所示。 7、插入SD卡&#xff0c;格式化为 8、考入BOOT.…

FPGA实现PCIE采集电脑端视频转SFP光口万兆UDP输出,基于XDMA+GTX架构,提供2套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案10G Ethernet Subsystem实现万兆以太网物理层方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图电脑端视频PCIE视频采集QT上位机XDMA配置及使用XDMA中断模块FDMA图像缓存UDP视频组包发送UDP协议栈MAC…

Mongo数据库集群搭建

目录 1、Mongo集群优势 1.1 高可用性 1.2 水平扩展性 1.3 高性能 1.4 灵活的架构设计 1.5 数据安全 1.6 管理与监控 2、下载指定操作系统版本包 3、部署和验证工作 3.1 准备配置文件及依赖 3.2 启动第一个节点 3.3 部署更多的节点 3.4 初始化副本集 3.5 设置管理…

创建vue3项目步骤

脚手架创建项目&#xff1a; pnpm create vue Cd 项目名称安装依赖&#xff1a;Pnpm iPnpm Lint&#xff1a;修复所有文件风格 &#xff0c;不然eslint语法警告报错要双引号Pnpm dev启动项目 拦截错误代码提交到git仓库&#xff1a;提交前做代码检查 pnpm dlx husky-in…

C语言项⽬实践-贪吃蛇

目录 1.项目要点 2.窗口设置 2.1mode命令 2.2title命令 2.3system函数 2.Win32 API 2.1 COORD 2.2 GetStdHandle 2.3 CONSOLE_CURSOR_INFO 2.4 GetConsoleCursorInfo 2.5 SetConsoleCursorInfo 2.5 SetConsoleCursorPosition 2.7 GetAsyncKeyState 3.贪吃蛇游戏设…