很多 Spring Boot 初学者都遇到过这种情况:明明修改了 application.yml,启动后却没有生效;同一个属性在多个文件中出现,最终值到底来自哪里;项目打成 JAR 后,又该如何在不重新打包的情况下修改端口?

本文通过一个可以亲手复现的 Demo,讲清楚 Spring Boot 默认配置文件的四个加载位置、覆盖优先级、互补配置以及最容易踩的坑。

一、实验环境

本文示例基于以下环境:

工具 版本
JDK 1.8
Spring Boot 2.0.2.RELEASE
Maven 3.x
配置格式 YAML

说明:本文严格按照 Spring Boot 2.0.2.RELEASE 的行为进行演示。Spring Boot 2.4 以后引入了新的 Config Data 机制,升级项目时应同时查看对应版本的官方文档。

二、先记住结论:四个默认位置

Spring Boot 启动时,会按照优先级从高到低扫描以下位置:

优先级 配置位置 含义
1 file:./config/ 当前运行目录下的 config 目录
2 file:./ 当前运行目录
3 classpath:/config/ 类路径下的 config 目录
4 classpath:/ 类路径根目录

可以简化记忆为:

项目外部 > 项目内部
config 目录 > 根目录

完整优先级:

file:./config/
        ↓
file:./
        ↓
classpath:/config/
        ↓
classpath:/

这里有两个非常重要的概念。

1. 什么是 classpath?

对于 Maven 项目,下面的目录会进入类路径:

src/main/resources

因此:

src/main/resources/application.yml

对应:

classpath:/application.yml

而:

src/main/resources/config/application.yml

对应:

classpath:/config/application.yml

2. 什么是 file:./?

file:./ 表示程序启动时的当前工作目录。

如果在 IDEA 中运行,通常是项目根目录;如果使用以下命令运行 JAR:

java -jar springBootDemo.jar

那么当前终端所在的目录就是 file:./

三、准备一个可复现的测试接口

为了准确观察属性来自哪个配置文件,我们定义两个属性:

config-demo:
  source: classpath-root
  author: qcby

controller 包中创建 ConfigLocationController

package com.qcby.springBootyml.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigLocationController {

    @Value("${config-demo.source}")
    private String source;

    @Value("${config-demo.author}")
    private String author;

    @GetMapping("/config-location")
    public String configLocation() {
        return "配置来源:" + source + ",作者:" + author;
    }
}

本文使用的端口为:

server:
  port: 8088

启动项目后访问:

http://localhost:8088/config-location

四、实验一:classpath 根目录

在以下位置创建配置文件:

src/main/resources/application.yml

内容:

server:
  port: 8088

config-demo:
  source: classpath-root
  author: qcby

启动项目,接口返回:

配置来源:classpath-root,作者:qcby

当前只有一个配置来源,因此没有发生覆盖。

五、实验二:classpath 的 config 目录

创建:

src/main/resources/config/application.yml

只配置 source

config-demo:
  source: classpath-config

重新启动后访问接口,结果变成:

配置来源:classpath-config,作者:qcby

为什么 source 变了,而 author 还在?

  • classpath:/config/ 的优先级高于 classpath:/,所以 source 被覆盖。
  • 高优先级文件没有配置 author,所以继续使用低优先级文件中的值。

这就是 Spring Boot 的两个核心规则:

相同属性:高优先级覆盖低优先级
不同属性:多个配置来源形成互补

六、实验三:当前项目根目录

在项目根目录创建 application.yml

springBootDemo
├─ application.yml       ← 新建在这里
├─ pom.xml
└─ src

内容:

config-demo:
  source: file-project-root

重新启动后访问接口:

配置来源:file-project-root,作者:qcby

这说明项目外部的 file:./application.yml 覆盖了类路径中的配置。

IDEA 中没有生效怎么办?

打开:

Run → Edit Configurations

确认 Working directory 是:

$PROJECT_DIR$

如果工作目录不是项目根目录,Spring Boot 自然无法在预期位置找到外部文件。

七、实验四:当前目录的 config 目录

在项目根目录创建:

config/application.yml

目录结构:

springBootDemo
├─ config
│  └─ application.yml    ← 默认位置中优先级最高
├─ application.yml
├─ pom.xml
└─ src

配置内容:

config-demo:
  source: file-config

重新启动,接口返回:

配置来源:file-config,作者:qcby

至此,四个位置的优先级已经全部验证完毕。

八、最终合并结果是如何产生的?

假设四个配置文件内容如下:

classpath:/application.yml

config-demo:
  source: classpath-root
  author: qcby

classpath:/config/application.yml

config-demo:
  source: classpath-config

file:./application.yml

config-demo:
  source: file-project-root

file:./config/application.yml

config-demo:
  source: file-config

Spring Boot 最终得到的配置相当于:

config-demo:
  source: file-config
  author: qcby

source 使用最高优先级的值,author 则从低优先级配置中补充。

所以 Spring Boot 并不是简单地“选择一个配置文件”,而是将多个 PropertySource 合并后,再按照优先级决定同名属性的最终值。

九、为什么外部配置特别重要?

打包之后,类路径中的配置会进入 JAR:

src/main/resources/application.yml
        ↓ Maven 打包
BOOT-INF/classes/application.yml

如果要修改 JAR 内部配置,通常需要重新打包。

而外部配置不在 JAR 中:

deploy
├─ config
│  └─ application.yml
└─ springBootDemo.jar

部署时可以直接修改外部配置:

server:
  port: 9000

然后重新启动 JAR,无需重新编译代码:

java -jar springBootDemo.jar

这也是开发、测试、生产环境使用同一份 JAR 的基础。

十、Profile 与配置位置如何配合?

如果激活 prod

--spring.profiles.active=prod

Spring Boot 还会查找:

application-prod.yml

在 Spring Boot 2.0.2 中,可以理解为:

JAR 外部的 Profile 配置
        > JAR 内部的 Profile 配置
        > JAR 外部的普通配置
        > JAR 内部的普通配置

例如,可以把通用默认值放进 JAR:

server:
  port: 8088

再在服务器外部提供生产配置:

server:
  port: 9000

这样既能保留默认值,又能根据部署环境覆盖关键参数。

十一、最容易踩的 7 个坑

1. 把外部配置误放进 resources

下面是类路径配置:

src/main/resources/config/application.yml

下面才是外部配置:

项目根目录/config/application.yml

两者不是同一个位置。

2. 忽略 IDEA 的 Working directory

file:./ 取决于工作目录,而不是 Java 文件所在目录。

3. 认为只会加载一个 application.yml

Spring Boot 会加载多个配置来源。同名属性覆盖,不同属性互补。

4. YAML 缩进错误

正确:

config-demo:
  source: file-config

错误:

config-demo:
 source:file-config

冒号后必须有空格,并且不要使用 Tab 缩进。

5. 同时使用 application.yml 和 application.properties

虽然 Spring Boot 支持两种格式,但不建议在同一位置同时维护两份同名配置,否则排查属性来源会变得困难。

6. 修改 resources 后没有重新构建

如果怀疑 target/classes 中的资源没有更新,可以执行:

mvn clean package

7. 忽略命令行参数的优先级

即使文件中配置:

server:
  port: 8088

下面的命令仍会将端口覆盖为 9000

java -jar springBootDemo.jar --server.port=9000

在 Spring Boot 2.0.2 中,命令行参数的优先级高于 application 配置文件。

十二、面试时怎么回答?

如果面试官问:“Spring Boot 默认从哪些位置加载配置文件,优先级如何?”

可以回答:

Spring Boot 默认会从当前目录的 config 子目录、当前目录、类路径的 config 目录以及类路径根目录加载 application.propertiesapplication.yml。优先级从高到低依次是 file:./config/file:./classpath:/config/classpath:/。多个配置来源会形成互补配置,同名属性由高优先级覆盖低优先级。

如果继续追问为什么要这样设计,可以回答:

类路径配置用于提供项目默认值,外部配置用于部署时覆盖默认值。这样可以让同一个 JAR 在不同环境中运行,而不需要重新修改和打包代码。

十三、总结

本文最重要的结论只有三条:

  1. 外部配置的优先级高于 JAR 内部配置。
  2. config 目录的优先级高于同级根目录。
  3. 相同属性会被覆盖,不同属性会形成互补。

再记一次完整顺序:

file:./config/
    > file:./
    > classpath:/config/
    > classpath:/

掌握这个顺序后,绝大多数“为什么我的 application.yml 没生效”的问题都能快速定位。

参考资料


推荐搜索关键词: Spring Boot 配置文件优先级、application.yml 加载顺序、Spring Boot config 目录、外部配置、classpath application.yml、Spring Boot 配置覆盖。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐