在Spring Boot多模块工程中,环境配置经常出现“明明改了application.yml却不生效”“切换环境时还要手工改一堆配置”这类问题。根本原因在于Spring Boot的配置加载机制和多模块的资源组织方式没有对齐。本文先把多模块下配置文件的加载范围讲清楚,再给出Maven Profile配合Spring Profile的完整配置过程,随后梳理配置优先级和常见的坑。

多模块下配置文件的作用范围与目录结构
Spring Boot在启动时会扫描classpath:/、classpath:/config/、file:./、file:./config/等位置的application.properties或application.yml,但它并不是把Maven多模块里每个子模块resources下的同名文件都合并进来。实际加载的是启动类所在jar包内部的resources,以及运行时工作目录下的外部配置文件。其他模块的application.yml如果不做特殊处理,通常不会被主配置加载,因为classpath下可能有多个同名资源,类加载器只会命中一个。
推荐做法是:所有与环境相关的application.yml集中放在启动模块(例如web模块)的src/main/resources目录;父模块和公共模块只放默认配置或通用配置,例如logback-spring.xml、banner.txt等。子模块中如果需要独立配置,可以使用spring.config.import导入,或者用@ConfigurationProperties配合@PropertySource明确指定。这样能避免多个模块的application.yml同时出现在classpath下造成加载顺序不可控。
目录结构示例如下:
parent-project
├── pom.xml
├── common-module
│ └── src/main/resources
│ └── common-defaults.yml
├── service-module
│ └── src/main/resources
│ └── service-extra.yml
└── web-module
└── src/main/resources
├── application.yml
├── application-dev.yml
├── application-test.yml
└── application-prod.yml
这种集中管理的方式牺牲了“每个模块自带配置”的灵活性,但换来了加载顺序清晰、环境切换可控。如果多个模块确实需要维护各自的默认值,可以保留在模块中,但在web-module的application.yml里通过spring.config.import=classpath:common-defaults.yml等方式手动导入,注意import路径需要带上classpath前缀。
配置步骤详解:Maven Profile与Spring Profile如何配合
多环境配置的目标是打包时决定运行环境,而不是运行时改大量文件。Maven负责构建期参数注入,Spring Boot负责运行期profile激活,两者通过resource filtering衔接。整个过程分为四步。
第一步,在父POM中定义环境profiles。父pom.xml里声明dev、test、prod三个profile,每个profile内定义一个spring.profiles.active属性,用于后续写入主配置文件。
<profiles>
<profile>
<id>dev</id>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<spring.profiles.active>test</spring.profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
每个profile的id对应Maven构建参数-P后面的值,属性值就是Spring Boot要激活的环境名。这样构建时使用mvn clean package -P test,Maven就会把test注入到后续的占位符中。
第二步,启动模块创建application.yml系列文件。主配置写通用配置与激活变量,注意这里使用了@spring.profiles.active@占位符,它是Maven resource filtering的标记。
# application.yml 主配置文件
spring:
profiles:
active: @spring.profiles.active@
application:
name: demo-app
server:
port: 8080
第三步,编写各环境配置文件。以application-dev.yml为例,这里只列出开发环境需要覆盖的配置项,未覆盖的会继续继承主配置。
# application-dev.yml 开发环境
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/dev_db
username: dev_user
password: dev_pass
test和prod文件的写法类似,只是端口、数据库地址、日志级别等不同。第四步,开启Maven资源过滤。这一步很容易遗漏,如果不开过滤,@spring.profiles.active@在打包后仍然是字面量,Spring Boot会把它当作普通字符串处理,导致profile激活失败。在web-module的pom.xml里加入如下配置。
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
注意,开启filtering后所有资源文件都会被Maven处理,如果yml中包含其他@变量或${},可能被误替换。更稳妥的做法是只对application*.yml开启过滤,静态资源保持false,可以用多个resource标签分别声明。例如先排除所有资源再单独包含application*.yml,这里不再展开,读者可根据项目情况调整。
配置加载优先级与覆盖规则
Spring Boot的配置来源有明确的优先级顺序,从高到低大致为:命令行参数、Java系统属性、操作系统环境变量、jar包外部的application-{profile}.properties/yml、jar包内部的application-{profile}.properties/yml、jar包外部的application.properties/yml、jar包内部的application.properties/yml。同一层级内,profile-specific文件优先级高于不带profile的文件。
| 优先级 | 配置来源 | 说明 |
|---|---|---|
| 1 | 命令行参数 | 如 --server.port=9090 |
| 2 | Java系统属性 | -Dserver.port=9090 |
| 3 | 操作系统环境变量 | SERVER_PORT |
| 4 | jar外 profile配置文件 | config/application-dev.yml |
| 5 | jar内 profile配置文件 | classpath:application-dev.yml |
| 6 | jar外 主配置文件 | config/application.yml |
| 7 | jar内 主配置文件 | classpath:application.yml |
多模块场景下尤其要注意,classpath内部同名配置文件的覆盖顺序并不确定,取决于类加载器返回资源的顺序。所以不要依赖多个模块中application.yml的合并,而应显式使用spring.config.import或集中管理。profile-specific文件里面的属性会覆盖主配置中的同名属性,但不会完全取代主配置,两者是叠加关系。例如主配置设置了server.port=8080,application-dev.yml中设置了server.port=8081,激活dev后端口为8081;而datasource只写在各profile中,主配置中没有,也能正常读取。
如果需要在运行时临时切换环境,可以通过命令行参数覆盖Maven构建时的默认值,例如:
java -jar demo-app.jar --spring.profiles.active=prod
这条命令会以prod环境启动,即使打包时默认注入的是dev,也会被命令行参数覆盖。理解这个优先级对于排查配置为什么不生效非常关键。
常见问题与注意事项
第一个高频问题是配置不生效。原因往往是profile没有激活成功。可以先在启动日志中查看“The following 1 profile is active: dev”确认。如果没有这行,检查Maven是否用了-P dev,是否开启过滤,打包后application.yml里@spring.profiles.active@是否被替换。如果打包后还是字面量,说明filtering未生效,重点检查resource配置是否位于正确的模块。
第二个问题是多个模块同名application.yml互相覆盖。解决方法是只在启动模块保留application.yml,其他模块如需配置,使用spring.config.import或自定义配置文件,不要用同名application.yml。否则会出现加载到哪个模块的文件不确定,不同机器上构建结果可能不一致。
第三个问题是敏感信息硬编码在配置文件中。生产环境建议通过环境变量或外部化配置覆盖,例如在应用启动脚本中注入DB_PASSWORD,配置文件中使用${DB_PASSWORD}。如果开启了Maven过滤,${}可能被Maven当属性解析,需要确认打包后的文件中占位符没有被误替换。通常Maven resource filtering默认只替换@...@语法,但不同插件版本行为可能有差异,最好打包后解压检查一次。
第四个问题是中文注释乱码。在pom.xml中设置project.build.sourceEncoding为UTF-8,并确保IDE和文件编码一致。此外,开启resource filtering时,如果yml文件中包含Maven变量如${project.build.finalName},过滤会替换,但Spring Boot的占位符${}也可能被误处理。为了安全,在资源文件中要使用${}时,可以用@...@代替或使用转义机制。
最后提醒一点,多模块多环境配置的核心不在“多”,而在“清晰”。集中管理配置文件、明确过滤范围、理解优先级叠加规则,再配合命令行参数灵活覆盖,绝大多数环境切换问题都能快速定位。打包后建议用解压工具确认最终的application.yml内容,这是最直接的验证手段。

