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_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,供应用运行时使用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本草纲目数字化:Spring Boot在中药实验管理中的应用

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理中药实验管理系统的相关信息成为必然。开发…

linux文件与重定向

目录 一、共识原理 二、回顾C语言文件函数 1.fopen 2.fwrite 3.fclose 三、文件系统调用 1.open 2.write 3.访问文件的本质 4.stdin&&stdout&&stderror 5.文件的引用计数 四、重定向 1.文件描述符的分配规则 2. 输出重定向 3.重定向系统调用 4.…

【微服务】SpringBoot 整合ELK使用详解

目录 一、前言 二、为什么需要ELK 三、ELK介绍 3.1 什么是elk 3.2 elk工作原理 四、ELK搭建 4.1 搭建es环境 4.1.1 获取es镜像 4.1.2 启动es容器 4.1.3 配置es参数 4.1.4 重启es容器并访问 4.2 搭建kibana 4.2.1 拉取kibana镜像 4.2.2 启动kibana容器 4.2.3 修改…

基于YOLOv8深度学习的汽车车身车损检测系统研究与实现(PyQt5界面+数据集+训练代码)

本文研究并实现了一种基于YOLOV8深度学习模型的汽车车身车损检测系统&#xff0c;旨在解决传统车损检测中效率低、精度不高的问题。该系统利用YOLOV8的目标检测能力&#xff0c;在单张图像上实现了车身损坏区域的精确识别和分类&#xff0c;尤其是在车身凹痕、车身裂纹和车身划…

ui->tableView升序

亮点 //设置可排序ui->tableView->setSortingEnabled(true);ui->tableView->sortByColumn(0,Qt::AscendingOrder); //排序void Widget::initTable() {//设置焦点策略:ui->tableView->setFocusPolicy(Qt::NoFocus);//显示网格线:ui->tableView->se…

Android Framework AMS(16)进程管理

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读AMS 进程方面的知识。关注思维导图中左上侧部分即可。 我们本章节主要是对Android进程管理相关知识有一个基本的了解。先来了解下L…

QT_CONFIG宏使用

时常在Qt代码中看到QT_CONFIG宏&#xff0c;之前以为和#define、DEFINES 差不多&#xff0c;看了定义才发现不是那么回事&#xff0c;定义如下&#xff1a; 看注释就知道了QT_CONFIG宏&#xff0c;其实是&#xff1a;实现了一个在编译时期安全检查&#xff0c;检查指定的Qt特性…

Spring Boot教程之Spring Boot简介

Spring Boot 简介 接下来一段时间&#xff0c;我会持续发布并完成Spring Boot教程 Spring 被广泛用于创建可扩展的应用程序。对于 Web 应用程序&#xff0c;Spring 提供了 Spring MVC&#xff0c;它是 Spring 的一个广泛使用的模块&#xff0c;用于创建可扩展的 Web 应用程序。…

Vue2教程001:初识Vue

文章目录 1、初识Vue1.1、Vue2前言1.2、创建Vue实例1.3、插值表达式1.4 Vue响应式特性 1、初识Vue 1.1、Vue2前言 Vue是什么&#xff1f; 概念&#xff1a;Vue是一个用于构建用户界面的渐进式框架。 Vue的两种使用方式&#xff1a; Vue核心包开发 场景&#xff1a;局部模块…

Linux手动安装nginx

本次以安装nginx-1.12.2为例 1、首先说明一下&#xff0c;安装nginx之前需要安装如下素材&#xff1a; 2、开始安装 第一步&#xff0c;安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel第二步&#xff0c;下载并安装nginx安装包&#xff08;n…

Element-ui Select选择器自定义搜索方法

效果图 具体实现 <template><div class"home"><el-selectref"currencySelect"v-model"currency"filterable:spellcheck"false"placeholder"请选择":filter-method"handleCurrencyFilter"change&q…

leetcode-44-通配符匹配

题解&#xff1a; 代码&#xff1a; 参考&#xff1a; (1)牛客华为机试HJ71字符串通配符 (2)leetcode-10-正则表达式匹配

C/C++中使用MYSQL

首先要保证下载好mysql的库和头文件&#xff0c;头文件在/usr/include/mysql/目录下&#xff0c;库在/usr/lib64/mysql/目录下&#xff1a; 一般情况下&#xff0c;在我们安装mysql的时候&#xff0c;这些都提前配置好了&#xff0c;如果没有就重装一下mysql。如果重装mysql还是…

Tryhackme练习-Wonderland

基本信息 由于tryhackme是在线靶场&#xff0c;所以这里的IP均为对方的内网IP 攻击机器&#xff1a;10.10.242.186 靶机&#xff1a;10.10.173.3 目标&#xff1a;获取2个flagroot权限 具体流程 信息收集 首先我们使用fscan进行端口扫描&#xff0c;fscan -h 10.10.173.…

SQL笔试题笔记(1)

下列选项中关于数据库事务的特性描述正确的是&#xff08;&#xff09; A.事务允许继续分割B.多个事务在执行事务前后对同一个数据读取的结果是不同的C.一个事务对数据库中数据的改变是暂时的D.并发访问数据库时&#xff0c;各并发事务之间数据库是独立的 答案解析&#xff1a…

vue3 如何调用第三方npm包内部的 pinia 状态管理库方法

抛砖引玉: 如果在开发vue3项目是, 引用了npm第三方包 ,而且这个包内使用了Pinia 状态管理库,那我们如何去调用 npm内部的 Pinia 状态管理库呢? 实际遇到的问题: 今天在制作npm包时遇到的问题,之前Vue2版本的时候状态管理库用的Vuex ,当时调用npm包内的状态管理库很简单,直接引…

麒麟KylinServer的网站,并部署一套主从DNS服务器提供域名解析服务

一、KylinServer网站搭建 ifconfig Copy 注意:根据实际网卡设备名称情况调整代码!不同环境下网卡名称略有不同! 获取本机IP地址,记住IP地址用于之后的配置填写。 ifconfig enp0s2 Copy 下载nginx源码包,并解压缩 wget http://10.44.16.102:60000/allfiles/Kylin/ng…

Python数据分析NumPy和pandas(三十五、时间序列数据基础)

时间序列数据是许多不同领域的结构化数据的重要形式&#xff0c;例如金融、经济、生态学、神经科学和物理学。在许多时间点重复记录的任何内容都会形成一个时间序列。许多时间序列是固定频率的&#xff0c;也就是说&#xff0c;数据点根据某些规则定期出现&#xff0c;例如每 1…

大数据常见面试题及答案(Linux、Zookeeper、Hadoop、Hive)

技术问答题目 一、Linux 1.如何给⽂件(⽂件夹)分配读r、w、x的操作权限&#xff1f; 2. vi 编辑器的常⽤命令有哪些&#xff1f; 3.Linux 中⽂件的操作权限分为⼏种&#xff1f; 4.Linux 中实时查看日志的方法 5. Linux查看内存、磁盘存储、io 读写、端口占用、进程等命…

【软件工程】一篇入门UML建模图(类图)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;软件开发必练内功_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…