2.3 Profile 与资源过滤:一套代码三副面孔 本节摘要:Profile 按 activation 条件向 POM 注入差异配置,资源过滤则在 process-resources 阶段把资源文件里的占位符替换为属性值,两者配合实现一套代码多环境打包。本节覆盖 Profile 的五种激活方式、过滤的开关配置、与 Spring 占位符的冲突规避,并吸收资源过滤的完整实战。 三个环境,三副面孔 risk-engine 要进三个环境:测试环境连 192.168 段的数据库、预发用云上测试库、生产用专线地址。最糙的做法是改配置打包三次,配置漂移与"测试包上了生产"的事故都从这里发芽。
本节摘要:Profile 按 activation 条件向 POM 注入差异配置,资源过滤则在 process-resources 阶段把资源文件里的占位符替换为属性值,两者配合实现一套代码多环境打包。本节覆盖 Profile 的五种激活方式、过滤的开关配置、与 Spring 占位符的冲突规避,并吸收资源过滤的完整实战。
risk-engine 要进三个环境:测试环境连 192.168 段的数据库、预发用云上测试库、生产用专线地址。最糙的做法是改配置打包三次,配置漂移与"测试包上了生产"的事故都从这里发芽。规范做法分两层:Profile 管差异的注入,资源过滤管差异的落盘——前者决定哪些属性生效,后者把属性写进配置文件。
先把两件武器各自的职责边界说死,因为混用正是多数事故的起点:
| 武器 | 职责 | 作用位置 | 误用后果 |
|---|---|---|---|
| Profile | 定义一组属性与配置差异 | POM 解析阶段 | 差异散落多处 无法审计 |
| 资源过滤 | 把占位符替换成属性值 | process-resources 阶段 | 敏感信息进版本库 泄密 |
Profile 可以定义属性、依赖、插件配置,激活方式五种。日常用前三种,后两种了解即可:
<profiles> <profile> <id>test-env</id> <!-- 激活方式一:命令行 -P 显式点名,最常用最可控 --> <properties> <db.host>192.168.10.21</db.host> <db.name>risk_test</db.name> </properties> </profile> <profile> <id>jdk11-build</id> <activation> <!-- 激活方式二:环境条件,JDK 版本前缀匹配 --> <jdk>11</jdk> </activation> </profile> <profile> <id>ci-env</id> <activation> <!-- 激活方式三:系统属性开关,流水线里 -Denv=ci 触发 --> <property> <name>env</name> <value>ci</value> </property> </activation> <!-- 激活方式四 os 按操作系统 激活方式五 file 按文件存在性 --> </profile> </profiles>
# 打测试包:显式激活 test-env mvn clean package -P test-env # 输出日志会出现:The following profiles are active: test-env # 流水线打 CI 包:属性触发 mvn clean package -Denv=ci
⚠️ 常见坑:默认激活的 Profile(activation 里写 activeByDefault)在"任何其他 Profile 被显式激活时"会静默失效。这是官方文档明载的行为,却坑了无数团队——CI 上 -P 点名了别的 Profile,默认环境配置悄悄退场,构建行为突变还没任何报错。替代方案:用属性激活加默认值,或干脆全部显式 -P,宁可多打几个字符。
属性注入后,落盘交给资源过滤。配置区两处开关:
<build> <filters> <!-- 过滤器文件:键值对形式的属性来源,敏感信息放这里且不进版本库 --> <filter>src/main/filters/filter-test.properties</filter> </filters> <resources> <resource> <directory>src/main/resources</directory> <!-- 开启过滤:该目录资源中的占位符会被替换 --> <filtering>true</filtering> <includes> <include>application.properties</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <!-- 不开启:二进制与无需替换的文件原样拷贝 --> <filtering>false</filtering> <excludes> <exclude>application.properties</exclude> </excludes> </resource> </resources> </build>
# src/main/filters/filter-test.properties:测试环境差异包 db.host=192.168.10.21 db.port=3306 db.name=risk_test
# src/main/resources/application.properties:占位符写法 db.url=jdbc连接头://${db.host}:${db.port}/${db.name} app.version=${project.version}
执行 mvn clean package -P test-env,target 目录里的 application.properties 已是替换后的成品:主机、端口、库名全部落位,project.version 这类 POM 内置属性同样可注入。一个高频翻车点要单独点名:过滤只应作用于文本资源。图标、证书等二进制文件若被 filtering 扫到,字节流被当文本改写,产出的文件直接损坏,报错还往往发生在运行期而非构建期。所以上面的配置用 includes 与 excludes 精确圈定范围,而不是无差别开过滤。

Spring Boot 项目里有一场经典遭遇战:Maven 过滤与 Spring 都使用 ${...} 占位符。Maven 在构建期先替换一轮,遇到 Spring 运行期才该解析的占位符(如 ${spring.profiles.active})会因"属性不存在"直接构建失败,或更糟——替换成空字符串埋进包里。
处置办法是给 Maven 换分隔符,让两套占位符语法隔离:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <!-- 定界符改为 @...@ 形式 且不覆盖既有分隔符 --> <delimiters> <delimiter>@</delimiter> </delimiters> <useDefaultDelimiters>false</useDefaultDelimiters> </configuration> </plugin>
改造后配置文件里 Maven 管的值写 @db.host@,Spring 管的值保留 ${...},互不越界。Spring Boot 官方的 starter-parent 正是这么约定的,遵循它就避开了大部分冲突场景。
💡 关键直觉:Profile 与过滤解决的是"构建期差异",Spring 的多配置文件解决的是"运行期差异"。原则是能晚绑定就晚绑定——环境地址这类运行期可变的值,优先交给 Spring 的配置体系,Maven 过滤只保留版本号这类"打进包里就定死"的值。把运行期信息硬灌进构建产物,等于给每次环境变更都强制安排一次重新打包。
包已经能按环境定制内容,但交付物的"形状"还没得选。下一节的 Assembly 插件负责物理形态:带依赖的目录分发包、含启动脚本的发行包,都能用一份描述文件定义出来。