1. 项目概述与核心价值
在Spring Boot项目的实际部署和运维中,配置文件的管理方式直接关系到应用的灵活性、安全性和运维效率。很多开发者,尤其是刚接触Spring Boot的朋友,习惯性地将application.properties或application.yml文件打包进最终的jar包里。这在开发阶段固然方便,但一旦进入生产环境,问题就来了:每次修改一个数据库连接地址或者调整一个日志级别,都得重新打包、重新部署整个应用,流程繁琐,风险也高。
这个问题的核心,其实就是如何实现配置的“外部化”。让配置文件独立于应用的可执行jar包,是迈向现代化、可运维应用的第一步。它不仅仅是把文件挪个位置那么简单,背后涉及到Spring Boot的配置加载机制、不同环境的优先级、安全考量以及如何与容器化、云原生等部署模式结合。今天,我们就来彻底拆解一下,把Spring Boot配置文件放在jar外部的几种主流方案,从最基础的命令行参数,到目录约定,再到结合配置中心的高级玩法,我会结合自己趟过的坑和实战经验,把每种方案的适用场景、具体操作和注意事项给你讲透。
无论你是负责单体应用部署的运维同学,还是正在设计微服务架构的开发者,理解并灵活运用这些配置外部化方案,都能让你的应用更健壮、更易于管理。接下来,我们就从Spring Boot配置加载的基本原理说起。
2. Spring Boot配置加载机制深度解析
要玩转外部配置,首先得知道Spring Boot是怎么“找”配置的。它并不是死板地只认classpath下的那个文件,而是有一套设计精巧、优先级分明的加载策略。理解这套策略,是你灵活运用所有外部化方案的基础。
Spring Boot使用一个叫Environment的抽象来代表应用运行环境,而PropertySource则是配置属性的来源。在应用启动时,Spring Boot会按照一个既定的顺序,从多个潜在的PropertySource中加载配置,后加载的配置会覆盖先加载的(相同key的情况下)。这个顺序就是配置的优先级。
官方文档给出了完整的优先级列表,但对于外部化配置,我们重点关注以下几个高优先级的来源:
- 命令行参数:通过
java -jar app.jar --server.port=8081传递的参数,优先级最高。 - 来自
java:comp/env的JNDI属性:主要在Java EE应用服务器中使用。 - Java系统属性:通过
-D参数设置,例如-Dspring.profiles.active=prod。 - 操作系统环境变量:例如在Linux中
export SPRING_APPLICATION_JSON='{"server":{"port":9090}}'。 - 随机值属性源:带有
random.*的配置。 - Profile-specific应用属性:位于jar包外的,例如
application-{profile}.properties或.yml。 - 应用属性:位于jar包外的
application.properties或.yml。 - Profile-specific应用属性:打包在jar内的。
- 应用属性:打包在jar内的。
注意:这里有一个关键点,位于jar包外的、非Profile-specific的配置文件(第7项),其优先级高于打包在jar内的、Profile-specific的配置文件(第8项)。这意味着,只要你把
application.properties放在jar包外面,它里面的配置就能覆盖jar包内application-prod.properties的配置。这个顺序是很多配置覆盖行为是否生效的决定因素。
除了优先级,另一个核心概念是默认的搜索路径。当你不做任何特殊指定时,Spring Boot启动时会自动在以下位置寻找名为application的配置文件(包括properties和yml格式):
classpath:(即jar包内部)classpath:/config/(即jar包内/config/目录下)file:./(当前jar文件所在的目录)file:./config/(当前jar文件所在目录的/config/子目录)file:./config/*/(当前jar文件所在目录的/config/的任何直接子目录)
这里的file:./指的就是运行java -jar命令时所在的当前工作目录。file:./config/的优先级又高于file:./。这为我们提供了两种最直接的外部化方案:放在与jar同目录,或者放在同目录的config子目录下。
2.1 配置属性绑定与刷新
了解加载顺序后,还需要知道配置是如何被应用消费的。Spring Boot通过@Value注解或@ConfigurationProperties注解将配置属性绑定到Bean的字段上。对于静态的、启动时即确定的配置,这种方式工作良好。
但对于需要动态刷新的配置(例如,在不重启应用的情况下修改日志级别),就需要引入Spring Cloud Config或者配合Spring Boot Actuator的@RefreshScope注解。需要注意的是,单纯将文件放在外部,并不天然支持动态刷新。动态刷新通常需要额外的机制(如文件监听或配置中心推送)来触发Bean的重新绑定。我们后续在方案中会提到如何实现简单的文件监听刷新。
3. 方案一:使用默认搜索路径(最简方案)
这是Spring Boot“开箱即用”支持的方式,无需任何代码改动,只需要在部署时遵循约定的目录结构即可。它非常适合传统虚拟机或物理机部署,对运维人员非常友好。
3.1 操作步骤与目录结构
假设你的应用打包后名为myapp.jar,你通过scp或ftp将其上传到了服务器的/opt/myapp/目录下。
方案1A:配置文件与jar同级
/opt/myapp/ ├── myapp.jar └── application.yml (或 application.properties)启动命令:cd /opt/myapp && java -jar myapp.jar
方案1B:配置文件置于config子目录(推荐)
/opt/myapp/ ├── myapp.jar └── config/ └── application.yml启动命令同样为:cd /opt/myapp && java -jar myapp.jar
第二种方式(config/子目录)的优先级更高,并且将配置与主程序分离,目录结构更清晰,是更推荐的做法。
3.2 多环境配置支持
这种方案完美支持Spring Profiles。你可以在外部目录放置不同环境的配置文件:
/opt/myapp/ ├── myapp.jar └── config/ ├── application.yml # 公共配置 ├── application-dev.yml # 开发环境配置 └── application-prod.yml # 生产环境配置启动时,通过命令行参数指定激活的Profile:java -jar myapp.jar --spring.profiles.active=prod。Spring Boot会先加载外部的application.yml,再加载外部的application-prod.yml,后者会覆盖前者的同名配置。
3.3 实操心得与避坑指南
- 绝对路径与相对路径:这里的
./是相对于启动时工作目录的。务必确保你的启动脚本(如systemd service文件或shell脚本)里,cd到了正确的目录,或者使用绝对路径来指定jar包位置。一个常见的坑是在systemd服务中,WorkingDirectory没有设置正确,导致应用找不到外部的配置文件。 - 配置覆盖的确定性:牢记优先级。如果你在jar包内的
application-prod.yml中定义了server.port=8080,但在jar包外的application.yml中定义了server.port=9090,那么最终端口将是9090。因为外部的非profile文件(优先级7)覆盖了内部的profile文件(优先级8)。 - 文件格式:
.properties和.yml可以混用,但同一个basename(如application)不建议混用。Spring Boot都能识别。个人更推荐YAML格式,因为结构更清晰,特别是对于复杂的嵌套配置。 - 权限与安全:确保运行Java进程的系统用户(如
appuser)对配置文件所在的目录和文件有读取权限。同时,配置文件可能包含密码等敏感信息,务必通过文件系统权限(如chmod 600 application.yml)严格控制访问。
提示:对于生产环境,我强烈建议使用
config/子目录的方案。它不仅优先级更高,而且当你要回滚或管理多个版本的配置时,你可以将整个config目录打包备份或替换,而不影响jar包本身,运维操作更清晰。
4. 方案二:通过命令行参数指定路径(灵活方案)
当默认的搜索路径不满足需求时,比如你的配置文件放在一个固定的、与jar包分离的集中目录(如/etc/myapp/),或者你有多个不同名称的配置文件需要加载,这时就需要通过启动参数来显式指定。
4.1 使用spring.config.location
这是最直接的方式。这个参数用于完全替代默认的搜索路径。
java -jar myapp.jar --spring.config.location=file:/etc/myapp/application.yml这条命令告诉Spring Boot:“不要再去./或./config/找了,直接去/etc/myapp/application.yml这个具体文件加载配置。”
如果你想指定一个目录,并让Spring Boot依然在这个目录里按application这个名字查找,需要在路径末尾加上/:
java -jar myapp.jar --spring.config.location=file:/etc/myapp/此时,Spring Boot会在/etc/myapp/目录下查找application.properties或application.yml。
4.2 使用spring.config.additional-location
这个参数更常用,也更友好。它不是在替代默认路径,而是在默认路径的基础上额外添加搜索位置,并且其优先级高于默认路径。
java -jar myapp.jar --spring.config.additional-location=file:/etc/myapp/启动后,配置加载顺序将是:
file:/etc/myapp/(优先级高,来自additional-location)file:./config/file:./classpath:/config/classpath:/
这样,你可以将最重要的、需要强制覆盖的配置放在/etc/myapp/下,同时依然保留项目内嵌的默认配置作为后备。这是一种“覆盖增强”模式。
4.3 指定多个位置与位置通配符
这两个参数都支持指定多个路径,用逗号分隔:
java -jar myapp.jar --spring.config.additional-location=file:/etc/myapp/,file:/opt/global-config/还支持通配符,用于加载一个目录下所有匹配的配置文件(常用于微服务共享配置):
java -jar myapp.jar --spring.config.additional-location=file:/etc/config/*/假设/etc/config/目录下有db-config/、redis-config/等子目录,每个子目录里都有application.yml,那么所有这些配置都会被加载。通配符目录的优先级低于明确指定的目录。
4.4 场景分析与实战技巧
- 场景:集中式配置管理:在公司内,你可能有一个专门的NFS共享目录或Git仓库,用于存放所有应用的基准生产配置。每个服务器部署应用时,都可以通过
--spring.config.additional-location=file:/mnt/shared-config/common/来加载这些公共配置(如公司Redis、MQ地址),然后再用本地的./config/目录下的配置覆盖具体的实例参数(如数据库连接池大小)。这样实现了配置的集中管理和本地定制。 - 技巧:在IDE中测试:在开发时,你也可以利用这个参数。比如在IntelliJ IDEA的“Run/Debug Configurations”的“Program arguments”里添加
--spring.config.additional-location=file:${PROJECT_DIR}/external-config/,来模拟测试外部配置加载,而无需修改代码或打包。 - 避坑:路径格式与协议:
file:前缀是必须的,用于表明是文件系统路径。路径可以是绝对路径(file:/etc/app/)或相对路径(file:../config/,相对的是工作目录)。在Windows系统上,路径格式为file:C:/path/to/config/或file:./config/。确保路径分隔符正确,并且应用有权限访问该路径。
5. 方案三:使用环境变量或系统属性(云原生友好方案)
在容器化(如Docker)和云平台(如Kubernetes)部署中,通过环境变量注入配置是最佳实践之一。它符合“十二要素应用”的原则,并且能很好地与Kubernetes的ConfigMap、Secret等资源集成。
5.1 Spring Boot对环境变量的映射规则
Spring Boot能够自动将操作系统环境变量绑定到Environment中。它遵循松散的绑定规则:将环境变量名的大写下划线格式转换为配置属性名的小写短横线格式。
例如:
- 环境变量
SPRING_DATASOURCE_URL→ 配置属性spring.datasource.url - 环境变量
MY_APP_SERVER_PORT→ 配置属性my.app.server.port
这意味着,你可以在Dockerfile中通过ENV指令,或者在docker run命令中通过-e参数,或者在Kubernetes Deployment的env字段中,直接设置这些环境变量来覆盖配置。
5.2 使用SPRING_CONFIG_ADDITIONAL_LOCATION环境变量
这是方案二的“环境变量版本”。你可以通过设置环境变量来指定额外的配置位置,而无需修改启动命令。
export SPRING_CONFIG_ADDITIONAL_LOCATION=file:/etc/myapp/ java -jar myapp.jar在Docker中,这非常有用:
FROM openjdk:11-jre-slim COPY target/myapp.jar /app.jar ENV SPRING_CONFIG_ADDITIONAL_LOCATION=file:/config/ ENTRYPOINT ["java", "-jar", "/app.jar"]然后运行容器时,将主机上的配置目录挂载到容器的/config路径:
docker run -v /host/path/config:/config myapp:latest5.3 使用SPRING_APPLICATION_JSON环境变量
这是一个特殊的环境变量,允许你直接通过JSON格式的字符串来提供配置。这在一些动态生成配置的场景下很方便。
export SPRING_APPLICATION_JSON='{"server":{"port":9090}, "logging":{"level":{"root":"WARN"}}}' java -jar myapp.jar这等同于在配置文件中设置了server.port=9090和logging.level.root=WARN。在Kubernetes中,你可以将ConfigMap的内容以JSON格式填入这个环境变量。
5.4 云原生部署实践
在Kubernetes中,标准的做法是:
- 将配置文件定义为ConfigMap。
- 将敏感信息(密码、密钥)定义为Secret。
- 在Pod定义中,将ConfigMap和Secret以卷(Volume)的形式挂载到容器内的某个路径(例如
/etc/app/config/)。 - 在容器的环境变量中,设置
SPRING_CONFIG_ADDITIONAL_LOCATION=file:/etc/app/config/,或者通过spring.config.import属性(Spring Boot 2.4+)来引入。
示例片段(Kubernetes Deployment YAML):
apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myapp image: myapp:latest env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: SPRING_CONFIG_ADDITIONAL_LOCATION # 方法一:环境变量指定 value: "file:/app-config/" volumeMounts: - name: app-config-volume mountPath: /app-config readOnly: true volumes: - name: app-config-volume configMap: name: myapp-config或者,在应用的application.yml中直接使用spring.config.import(Spring Boot 2.4+新特性,更现代):
# 在 jar 包内的 application.yml 中 spring: config: import: optional:file:/app-config/application.yml[.properties]这样,配置的挂载和加载就完全解耦了。
注意:在容器环境中,要特别注意配置文件的行尾格式(LF vs CRLF)和字符编码(UTF-8),避免因格式问题导致配置解析错误。另外,通过环境变量传递的配置,如果值中包含特殊字符(如
@、#、:),可能需要适当的转义或使用引号包裹。
6. 方案四:编程式指定与自定义配置源(高级定制)
对于有特殊需求的场景,例如需要从数据库、远程HTTP接口或自定义加密文件中加载配置,我们可以通过编程方式介入Spring Boot的配置加载过程。
6.1 实现EnvironmentPostProcessor
这是最强大、最底层的方式之一。EnvironmentPostProcessor接口允许你在SpringEnvironment准备好之后、ApplicationContext创建之前,修改或添加配置属性源。
实战示例:从外部加密文件加载配置
假设我们有一个加密的配置文件config.enc,放在/secure/config/目录下,我们需要在启动时解密并加载它。
创建处理器类:
package com.example.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.io.FileSystemResource; import org.springframework.util.StreamUtils; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; public class EncryptedConfigEnvironmentPostProcessor implements EnvironmentPostProcessor { // 一个简单的“解密”函数示例(实际请使用安全的解密库,如Jasypt或与KMS集成) private String decrypt(String encrypted) { // 这里实现你的解密逻辑,例如AES解密 // 示例:简单反转字符串(仅为演示,绝对不要用于生产!) return new StringBuilder(encrypted).reverse().toString(); } @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { String configPath = "/secure/config/config.enc"; FileSystemResource resource = new FileSystemResource(configPath); if (resource.exists()) { try (InputStream is = resource.getInputStream()) { String encryptedContent = StreamUtils.copyToString(is, StandardCharsets.UTF_8); String decryptedContent = decrypt(encryptedContent); // 假设解密后是标准的properties格式内容 // 这里需要解析properties格式字符串到Map Map<String, Object> propertiesMap = parseProperties(decryptedContent); // 创建一个自定义的PropertySource,并设置较高的优先级 MapPropertySource encryptedPropertySource = new MapPropertySource("encryptedConfig", propertiesMap); environment.getPropertySources().addFirst(encryptedPropertySource); // addFirst确保最高优先级 } catch (IOException e) { // 处理异常,例如记录日志,但不要抛出以免阻止启动 System.err.println("WARN: Failed to load encrypted config from " + configPath + ", error: " + e.getMessage()); } } } private Map<String, Object> parseProperties(String content) { Map<String, Object> map = new HashMap<>(); String[] lines = content.split("\n"); for (String line : lines) { line = line.trim(); if (!line.isEmpty() && !line.startsWith("#")) { int eqIndex = line.indexOf('='); if (eqIndex > 0) { String key = line.substring(0, eqIndex).trim(); String value = line.substring(eqIndex + 1).trim(); map.put(key, value); } } } return map; } }注册处理器:在
src/main/resources/META-INF/目录下创建spring.factories文件,并添加:org.springframework.boot.env.EnvironmentPostProcessor=com.example.config.EncryptedConfigEnvironmentPostProcessor
这样,应用启动时就会自动执行这个处理器,加载并解密外部加密文件,并将其配置以最高优先级加入环境中。
6.2 使用@PropertySource注解
对于更简单的、需要加载特定名称的非标准配置文件,可以在主配置类或任何@Configuration类上使用@PropertySource注解。
@SpringBootApplication @PropertySource(value = "file:${CONFIG_HOME}/my-special.properties", ignoreResourceNotFound = true) public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }这里通过系统属性CONFIG_HOME来指定文件所在目录。ignoreResourceNotFound = true使得当文件不存在时不会报错。需要注意的是,@PropertySource默认只支持.properties文件,如果需要加载YAML,需要配合一个自定义的PropertySourceFactory。
6.3 方案对比与选型建议
| 特性 | 默认搜索路径 | 命令行/环境变量指定 | 编程式/自定义 |
|---|---|---|---|
| 复杂度 | 极低,无需任何改动 | 低,仅需启动参数 | 高,需要开发代码 |
| 灵活性 | 低,受限于固定路径 | 中,可指定任意路径 | 极高,可接入任意来源 |
| 动态刷新 | 需借助spring.cloud.config或自定义监听 | 需借助spring.cloud.config或自定义监听 | 可自行实现监听逻辑 |
| 适用场景 | 传统部署,配置稳定 | 容器化部署,多环境 | 安全加密配置、远程配置、数据库配置等特殊需求 |
| 运维友好 | 非常友好 | 友好 | 需要开发介入,对运维不透明 |
选型心法:
- KISS原则优先:如果没有特殊需求,方案一(
config/子目录)是生产环境的首选。它简单、可靠、符合约定,任何运维人员都能看懂。 - 云原生选环境变量:如果使用Docker/K8s部署,方案三(环境变量)是主流,配合ConfigMap卷挂载,是云原生标准实践。
- 特殊需求才编码:只有当你需要处理加密、需要从非文件源(数据库、HTTP API)拉取配置、或者有复杂的配置合并逻辑时,才考虑方案四。并且要谨慎评估,因为自定义代码增加了复杂性和维护成本。
7. 配置动态刷新与监控实践
将配置放在外部,我们自然希望修改配置后,应用能部分或全部生效,而无需重启。Spring Boot Actuator和Spring Cloud提供了相关支持。
7.1 基于Spring Boot Actuator的简单刷新
对于通过@ConfigurationProperties或@Value注入的配置,如果要支持动态刷新,需要:
- 在依赖中引入
spring-boot-starter-actuator。 - 在需要刷新的Bean上添加
@RefreshScope注解。 - 暴露Actuator的
refresh端点(在application.yml中配置:management.endpoints.web.exposure.include=refresh,health,info)。 - 修改外部配置文件后,向应用发送一个HTTP POST请求:
curl -X POST http://localhost:8080/actuator/refresh。
这会使所有标注了@RefreshScope的Bean被重新创建,新的配置值随之注入。但请注意,这需要你主动触发refresh端点。它不会自动监听文件变化。
7.2 实现文件变更监听(简易版)
要实现真正的“热更新”,可以编写一个后台线程,定期检查配置文件的最后修改时间。当文件发生变化时,调用/actuator/refresh端点或者直接发布一个RefreshScopeRefreshedEvent事件。
示例代码片段(需自行完善异常处理和线程池管理):
@Component public class ConfigFileWatcher { @Autowired private ApplicationContext context; private File configFile; private long lastModified; @PostConstruct public void init() { // 假设配置文件路径,可从环境变量获取 String path = environment.getProperty("spring.config.additional-location", "file:./config/application.yml"); configFile = new File(path.replace("file:", "")); lastModified = configFile.lastModified(); ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(this::checkAndReload, 30, 30, TimeUnit.SECONDS); } private void checkAndReload() { long currentModified = configFile.lastModified(); if (currentModified > lastModified) { lastModified = currentModified; // 发布刷新事件 context.publishEvent(new RefreshScopeRefreshedEvent()); // 或者调用RefreshEndpoint (需要注入) // refreshEndpoint.refresh(); log.info("External configuration file changed, refreshed @RefreshScope beans."); } } }7.3 集成Spring Cloud Config
对于大型分布式系统,更专业的做法是使用Spring Cloud Config Server。它将所有微服务的配置文件集中存储在Git、SVN或文件系统中。Config Client(即你的Spring Boot应用)在启动时和定期从Server拉取配置。
此时,外部化配置的层次变为:
- Config Server远程配置(最高优先级,支持动态刷新)
- 本地
spring.config.additional-location指定的文件 - jar包外部的
application-{profile}.yml - jar包外部的
application.yml - jar包内部的配置
当Git仓库中的配置文件变更后,可以通过Webhook触发Config Server的/actuator/bus-refresh端点(需集成Spring Cloud Bus),批量刷新所有关联的微服务。这是微服务架构下配置管理的完整解决方案。
8. 安全、权限与最佳实践汇总
将配置剥离到外部,安全性和权限管理至关重要。
敏感信息加密:
- 绝不将明文密码、API密钥写入配置文件。
- 使用Jasypt等库对配置文件中的敏感值进行加密。加密后的密文放在外部配置文件中,启动时通过环境变量或命令行参数传入解密密钥。
- 在K8s环境中,敏感信息务必使用Secret对象存储,并以卷或环境变量方式注入。
文件系统权限:
- 配置文件的权限应设置为
640(-rw-r-----),即所有者可读写,同组用户只读,其他用户无权限。 - 运行应用的用户(如
appuser)应该属于拥有读取权限的组。 - 配置目录的权限通常设置为
750(drwxr-x---)。
- 配置文件的权限应设置为
配置版本化:
- 将外部配置文件纳入版本控制系统(如Git),但确保.gitignore排除了包含真实密码的配置文件。可以提交一个
application-template.yml示例文件,真实配置通过CI/CD流程生成或从安全存储中获取。 - 在部署时,通过CI/CD管道将对应版本的配置文件放置到服务器指定位置。
- 将外部配置文件纳入版本控制系统(如Git),但确保.gitignore排除了包含真实密码的配置文件。可以提交一个
配置分离策略:
- 按稳定性分离:几乎不变的配置(如框架设置)可以放在jar包内。环境相关的(数据源、Redis)放在外部。
- 按安全性分离:非敏感配置(如服务器端口)可以放在普通外部文件。敏感配置(密码)通过环境变量或加密文件提供。
- 按作用域分离:全局配置(公司中间件地址)可以通过
spring.config.additional-location从一个公共目录加载。应用特有配置放在自己的./config/目录下。
启动脚本模板示例:
#!/bin/bash APP_HOME=/opt/myapp APP_JAR=myapp.jar CONFIG_DIR=${APP_HOME}/config LOG_DIR=/var/log/myapp # 设置额外的配置位置,并确保存在 export SPRING_CONFIG_ADDITIONAL_LOCATION=file:${CONFIG_DIR}/ # 激活生产Profile export SPRING_PROFILES_ACTIVE=prod # 如有加密,传入解密密码(密码本身应从更安全的地方获取,如启动时从KMS读取) # export JASYPT_ENCRYPTOR_PASSWORD=your_master_password cd ${APP_HOME} exec java -Xms512m -Xmx1024m \ -Djava.security.egd=file:/dev/./urandom \ -jar ${APP_JAR} \ >> ${LOG_DIR}/console.log 2>&1这个脚本定义了清晰的目录,设置了关键的环境变量,并完成了基本的JVM参数配置。
经过以上几个方案的详细拆解,相信你已经对Spring Boot外部化配置有了全面且深入的理解。从最简单的目录约定到复杂的编程定制,每种方案都有其用武之地。我的经验是,在满足需求的前提下,尽量选择更简单、更标准的方案,这样无论是对于团队协作还是长期维护,都更加有利。配置管理是应用可运维性的基石,花点时间把它设计好,后续的运维工作会轻松很多。如果在实践中遇到具体问题,多从Spring Boot的Environment加载优先级和PropertySource的机制去思考,往往就能找到答案。