文章目录
- [1. Maven属性替换](#1. Maven属性替换)
-
- [1.1Maven 构建生命周期中的资源过滤](#1.1Maven 构建生命周期中的资源过滤)
- [1.2Maven Properties 替换机制](#1.2Maven Properties 替换机制)
-
- [1.2.1 配置流程](#1.2.1 配置流程)
-
- [第一步:在 POM 中定义 Properties](#第一步:在 POM 中定义 Properties)
- [第二步:在 application.yml 中使用占位符](#第二步:在 application.yml 中使用占位符)
- 第三步:启用资源过滤
- 第四步:执行构建
- [2. 替换过程示例](#2. 替换过程示例)
- 2.属性查找与继承规则
-
- 2.1.多模块项目中的属性继承
- 2.2属性查找优先级(从高到低)
- [2.3实际案例:module-a 模块](#2.3实际案例:module-a 模块)
- 2.4属性覆盖示例
- 3.两种占位符的区别
- 4.常见问题与解决方案
-
- [问题 1:占位符未被替换](#问题 1:占位符未被替换)
- [问题 2:多模块构建失败](#问题 2:多模块构建失败)
- [问题 3:Profile 未激活](#问题 3:Profile 未激活)
- [问题 4:Spring 占位符解析失败](#问题 4:Spring 占位符解析失败)
- [问题 5:IDE 中显示占位符错误](#问题 5:IDE 中显示占位符错误)
- 5.最佳实践
-
- 5.1合理使用两种占位符
- 5.2.集中管理版本号
- [5.3.使用 Flatten Maven Plugin](#5.3.使用 Flatten Maven Plugin)
- [5. 4 Profile 命名规范](#5. 4 Profile 命名规范)
- 5.5敏感信息管理
- 5.6资源过滤性能优化
- [5.7使用 BOM 管理依赖](#5.7使用 BOM 管理依赖)
- 6.附录:常用命令速查
-
- [6.1Maven 构建命令](#6.1Maven 构建命令)
- 6.2调试技巧
- 7.总结
1. Maven属性替换
1.1Maven 构建生命周期中的资源过滤
Maven 在构建过程中会经历多个阶段,其中资源处理阶段 (process-resources)负责将 src/main/resources 目录下的文件复制到 target/classes 目录,并可以选择性地对其中的占位符进行替换。
validate → initialize → generate-sources → process-sources
→ generate-resources → process-resources → compile → ...
↑
资源过滤发生在这里
1.2Maven Properties 替换机制
1.2.1 配置流程
第一步:在 POM 中定义 Properties
根 pom.xml:
xml
<properties>
<revision>1.0.0</revision>
<java.version>17</java.version>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
<profiles>
<profile>
<id>dev</id>
<properties>
<profiles.active>dev</profiles.active>
<config.server>192.168.1.100:8848</config.server>
<config.username>admin</config.username>
<config.password>password123</config.password>
</properties>
<activation>
<!-- 默认环境 -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile id="prod">
<id>dev</id>
<properties>
<profiles.active>prod</profiles.active>
<config.server>10.0.0.100:8848</config.server>
</properties>
</profile>
</profiles>
第二步:在 application.yml 中使用占位符
yaml
spring:
profiles:
active: @profiles.active@
cloud:
config:
server-addr: @config.server@
username: @config.username@
password: @config.password@
第三步:启用资源过滤
pom.xml 的 build 配置:
xml
<build>
<resources>
<!-- 不进行过滤的资源 -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
<!-- 需要进行属性替换的资源 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>application*.yml</include>
<include>application*.properties</include>
<include>bootstrap*.yml</include>
<include>logback*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
第四步:执行构建
bash
# 使用 dev 环境(默认)
mvn clean package
# 或显式指定环境
mvn clean package -P dev
mvn clean package -P prod
2. 替换过程示例
构建前(源代码):
yaml
spring:
profiles:
active: @profiles.active@
cloud:
config:
server-addr: @config.server@
构建后(target/classes/application.yml):
yaml
spring:
profiles:
active: dev
cloud:
config:
server-addr: 192.168.1.100:8848
2.属性查找与继承规则
2.1.多模块项目中的属性继承
my-project (根模块)
├── my-modules (父模块)
│ ├── module-a (子模块)
│ ├── module-b (子模块)
│ └── module-c (子模块)
├── module-auth (子模块)
└── module-gateway (子模块)
2.2属性查找优先级(从高到低)
┌─────────────────────────────────────────┐
│ 1. 当前模块 POM 的 <properties> │ ← 最高优先级
├─────────────────────────────────────────┤
│ 2. 当前模块激活的 Profile 中的 │
│ <properties> │
├─────────────────────────────────────────┤
│ 3. 父模块 POM 的 <properties> │ ← 通过继承
├─────────────────────────────────────────┤
│ 4. 父模块激活的 Profile 中的 │
│ <properties> │
├─────────────────────────────────────────┤
│ 5. 祖父模块(根 POM)的 <properties> │ ← 最终 fallback
├─────────────────────────────────────────┤
│ 6. 根 POM 激活的 Profile 中的 │
│ <properties> │
└─────────────────────────────────────────┘
2.3实际案例:module-a 模块
场景: module-a/src/main/resources/application.yml 中有 @profiles.active@
查找路径:
第1步: module-a/pom.xml
↓
没有定义 <profiles.active>
第2步: my-modules/pom.xml (父模块)
↓
没有定义 <profiles.active>
第3步: my-project/pom.xml (根模块/祖父模块) ✅
↓
在 <profile id="dev"> 中找到
→ <profiles.active>dev</profiles.active>
2.4属性覆盖示例
子模块可以覆盖父模块的属性:
xml
<!-- 根 pom.xml -->
<properties>
<custom.property>root-value</custom.property>
</properties>
<!-- 子模块 pom.xml -->
<properties>
<!-- 覆盖父模块的值 -->
<custom.property>child-value</custom.property>
</properties>
结果: 子模块及其资源文件中 @custom.property@ 会被替换为 child-value
3.两种占位符的区别
3.1对比表(!!!)
| 特性 | Maven 占位符 @xxx@ |
Spring 占位符 ${xxx} |
|---|---|---|
| 处理时机 | Maven 构建时 | Spring Boot 运行时 |
| 数据来源 | POM 文件的 <properties> |
application.yml、环境变量、系统属性等 |
| 是否可动态修改 | ❌ 打包后固定 | ✅ 可通过启动参数修改 |
| 典型用途 | 环境配置、版本号 | 运行时配置、外部化配置 |
| 处理工具 | Maven Resource Plugin | Spring PropertyPlaceholderConfigurer |
3.2代码示例
yaml
server:
port: 8080
spring:
application:
name: my-application
# ✅ Maven 占位符 - 构建时确定,不同环境打不同的包
profiles:
active: @profiles.active@
cloud:
config:
# ✅ Maven 占位符 - 配置中心地址在构建时确定
server-addr: @config.server@
username: @config.username@
password: @config.password@
---
spring:
datasource:
dynamic:
# ✅ Spring 占位符 - 可以从其他配置文件或环境变量获取(获取不到时,使用默认值master)
primary: ${datasource.primary:master}
datasource:
master:
type: ${spring.datasource.type}
url: ${datasource.master.url}
username: ${datasource.master.username}
password: ${datasource.master.password}
3.2重要说明
Maven 占位符不会从 application.yml 中获取值!
❌ 错误理解: "如果 POM 中找不到,就从 yml 中找"
✅ 正确理解:
- Maven 构建时只从 POM 的 properties 中取值
- 如果整个继承链都找不到,Maven 会:
- 保留占位符原样(默认行为)
- 或者构建失败(如果配置了严格模式)
- 绝不会从 application.yml 中补充值
3.2实际案例分析
案例 1:多环境配置(!!!)
项目结构
my-project/
├── pom.xml (根 POM,定义了 dev/prod 两个 profile)
├── module-auth/
│ └── src/main/resources/application.yml
└── module-service/
└── src/main/resources/application.yml
根 POM 配置
xml
<profiles>
<profile id="dev">
<properties>
<profiles.active>dev</profiles.active>
<config.server>192.168.1.100:8848</config.server>
<logstash.address>192.168.1.100:4560</logstash.address>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile id="prod">
<properties>
<profiles.active>prod</profiles.active>
<config.server>10.0.0.100:8848</config.server>
<logstash.address>10.0.0.100:4560</logstash.address>
</properties>
</profile>
</profiles>
子模块 application.yml
yaml
spring:
profiles:
active: @profiles.active@
cloud:
config:
server-addr: @config.server@
logging:
logstash:
address: @logstash.address@
构建不同环境的包
bash
# 构建开发环境(默认)
mvn clean package
# 结果:profiles.active=dev, config.server=192.168.1.100:8848
# 构建生产环境
mvn clean package -P prod
# 结果:profiles.active=prod, config.server=10.0.0.100:8848
案例 2:版本统一管理
根 POM 定义版本
xml
<properties>
<revision>1.0.0</revision>
<spring-boot.version>3.2.0</spring-boot.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
</properties>
子模块引用
xml
<parent>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>${revision}</version>
</parent>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies>
优势: 所有模块的版本在根 POM 统一管理,升级只需修改一处。
4.常见问题与解决方案
问题 1:占位符未被替换
现象: 打包后的 yml 文件中仍然是 @profiles.active@
可能原因:
- 未启用资源过滤(
<filtering>false</filtering>) - POM 中没有定义对应的 property
- 使用了错误的 profile
解决方案:
xml
<!-- 确保启用了过滤 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>application*.yml</include>
</includes>
<filtering>true</filtering>
</resource>
<!-- 确保定义了属性 -->
<properties>
<profiles.active>dev</profiles.active>
</properties>
问题 2:多模块构建失败
现象: 子模块找不到父模块定义的 BOM 依赖
错误信息:
Could not find artifact com.example:my-common-bom:pom:${revision}
原因: 内部 BOM 依赖无法从远程仓库下载
解决方案: 必须从根目录构建
bash
# ✅ 正确:从根目录构建
cd my-project
mvn clean install
# ❌ 错误:直接从子模块构建
cd my-project/module-service
mvn clean install
问题 3:Profile 未激活
现象: 使用了默认的 property 值,而不是 profile 中的值
检查方法:
bash
# 查看当前激活的 profile
mvn help:active-profiles
# 显式激活 profile
mvn clean package -P dev
问题 4:Spring 占位符解析失败
现象: 启动时报错 Could not resolve placeholder 'xxx'
原因: Spring 占位符 ${xxx} 在运行时无法找到对应的值
解决方案:
yaml
# 方式1:提供默认值
spring:
datasource:
type: ${datasource.type:com.zaxxer.hikari.HikariDataSource}
# 方式2:在 application.yml 中定义
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 方式3:通过启动参数传入
java -jar app.jar --datasource.type=com.zaxxer.hikari.HikariDataSource
问题 5:IDE 中显示占位符错误
现象: IDEA 中 yml 文件的 @xxx@ 显示红色警告
原因: IDE 不理解 Maven 占位符语法
解决方案:
- 忽略警告(不影响构建)
- 安装 Maven Helper 插件
- 使用 Maven 视图刷新项目
5.最佳实践
5.1合理使用两种占位符
yaml
# ✅ 推荐:构建时确定的配置用 Maven 占位符
spring:
profiles:
active: @profiles.active@
cloud:
config:
server-addr: @config.server@
# ✅ 推荐:运行时可能变化的配置用 Spring 占位符
spring:
datasource:
url: ${DB_URL:jdbc:mysql://localhost:3306/mydb}
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:password}
5.2.集中管理版本号
根 POM:
xml
<properties>
<!-- 项目版本 -->
<revision>1.0.0</revision>
<!-- 框架版本 -->
<spring-boot.version>3.2.0</spring-boot.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<!-- 第三方库版本 -->
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<redisson.version>3.35.0</redisson.version>
</properties>
5.3.使用 Flatten Maven Plugin
作用: 解决 CI/CD 中 ${revision} 占位符的问题
xml
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.3.0</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
5. 4 Profile 命名规范
xml
<profiles>
<!-- 开发环境 -->
<profile>
<id>dev</id>
<properties>
<profiles.active>dev</profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 测试环境 -->
<profile>
<id>test</id>
<properties>
<profiles.active>test</profiles.active>
</properties>
</profile>
<!-- 预发布环境 -->
<profile>
<id>staging</id>
<properties>
<profiles.active>staging</profiles.active>
</properties>
</profile>
<!-- 生产环境 -->
<profile id="prod">
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
</properties>
</profile>
</profiles>
5.5敏感信息管理
❌ 不要硬编码在 POM 中:
xml
<properties>
<db.password>123456</db.password> <!-- 危险! -->
</properties>
✅ 推荐做法:
方式1:使用 Maven settings.xml
xml
<!-- ~/.m2/settings.xml -->
<servers>
<server>
<id>db-config</id>
<username>root</username>
<password>${env.DB_PASSWORD}</password>
</server>
</servers>
方式2:使用环境变量
bash
export DB_PASSWORD=secure_password
mvn clean package
方式3:使用 Jasypt 加密
yaml
spring:
datasource:
password: ENC(encrypted_value_here)
5.6资源过滤性能优化
只过滤需要的文件:
xml
<resources>
<!-- 大部分资源不过滤(提高构建速度) -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>application*.yml</exclude>
<exclude>bootstrap*.yml</exclude>
</excludes>
</resource>
<!-- 只过滤配置文件 -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>application*.yml</include>
<include>bootstrap*.yml</include>
</includes>
</resource>
</resources>
5.7使用 BOM 管理依赖
根 POM:
xml
<dependencyManagement>
<dependencies>
<!-- 自定义 BOM -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common-bom</artifactId>
<version>${revision}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子模块(无需指定版本):
xml
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common-core</artifactId>
<!-- 版本由 BOM 管理 -->
</dependency>
</dependencies>
6.附录:常用命令速查
6.1Maven 构建命令
bash
# 清理并编译
mvn clean compile
# 清理并打包
mvn clean package
# 清理并安装到本地仓库
mvn clean install
# 跳过测试打包
mvn clean package -DskipTests
# 指定 profile
mvn clean package -P prod
# 查看激活的 profile
mvn help:active-profiles
# 查看有效 POM
mvn help:effective-pom
# 查看属性值
mvn help:evaluate -Dexpression=project.version
6.2调试技巧
bash
# 详细输出(查看属性替换过程)
mvn clean package -X
# 只构建特定模块
mvn clean install -pl module-service -am
# 从根目录构建所有模块
mvn clean install
7.总结
7.1核心要点
- Maven 属性替换发生在构建时,不是运行时
- 属性通过继承机制传递,优先级从子模块到父模块再到根模块
@xxx@和${xxx}有本质区别,前者是 Maven 处理,后者是 Spring 处理- 多模块项目必须从根目录构建,确保内部依赖正确解析
- 合理设计 Profile 和 Properties,实现灵活的多环境配置
7.2知识图谱
Maven 配置体系
├── Properties 定义
│ ├── 全局 Properties
│ └── Profile-specific Properties
├── 资源过滤
│ ├── filtering=true/false
│ ├── includes/excludes
│ └── 占位符替换 (@xxx@)
├── Profile 管理
│ ├── activation (自动激活)
│ ├── manual activation (-P)
│ └── environment-based
└── 多模块继承
├── parent-child 关系
├── 属性继承
└── 依赖管理 (BOM)
Spring Boot 配置
├── application.yml
├── Property Placeholder (${xxx})
├── Environment Variables
├── Command-line Arguments
└── External Configuration