Maven 只会引依赖?这 9 个 Profile 企业级神仙用法,让你的构建效率提升 10 倍!

你是否经历过:上线前手动改数据库地址?Windows 写好的 Shell 脚本推到 Linux 构建就报错?本地构建几秒钟,加上代码扫描就要等几分钟?
今天我们将深度拆解 Maven Profile 的 9 大企业级应用场景。从基础的环境隔离,到高阶的包形态切换跨平台钩子插件级差异化,代码全部开源,建议收藏!


基础篇:环境与变量管理

场景 1:多环境配置零失误 (Dev / Test / Prod)

这是 Profile 最经典的应用。坚决杜绝在代码里写死 IP 地址!

配置思路
不要直接在 pom.xml 里写死配置,而是通过 Profile 激活特定的资源文件目录。

<profiles>
    <profile>
        <id>dev</id>
        <activation><activeByDefault>true</activeByDefault></activation>
        <properties><env>dev</env></properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties><env>prod</env></properties>
    </profile>
</profiles>

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes><exclude>application-*.properties</exclude></excludes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes><include>application-${env}.properties</include></includes>
        </resource>
    </resources>
</build>

效果mvn package -Pprod,生产配置自动生效,测试配置绝不泄露。

场景 2:配置文件的“动态变量替换” (@code@ vs $)

痛点分析
有时候我们不想维护 application-dev.ymlapplication-prod.yml 等一堆文件,或者有些敏感信息(如构建时间、Git版本号、数据库账号)需要由 Maven 在打包时动态注入到配置文件中。

核心机制
利用 Maven 的 <filtering>true</filtering> 开启资源过滤,Maven 会在打包时扫描配置文件,将占位符替换为 Profile 中定义的 <properties> 值。

1. POM 配置 (开启过滤与定义变量)

首先,在 Profile 中定义差异化变量,并务必在 <build> 中开启资源过滤:

<profiles>
    <profile>
        <id>dev</id>
        <activation><activeByDefault>true</activeByDefault></activation>
        <properties>
            <!-- 自定义变量 -->
             <db.url>jdbc:mysql://127.0.0.1:3306/dev_db</db.url>
            <log.level>DEBUG</log.level>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <db.url>jdbc:mysql://192.168.0.100:3306/prod_db</db.url>
            <log.level>INFO</log.level>
        </properties>
    </profile>
</profiles>

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <!-- ⚠️ 必须开启 filtering,否则变量无法替换 -->
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

2. 在 .properties 文件中使用 (标准写法)

对于传统的 .properties 文件,直接使用 Maven 标准的 ${variable} 语法即可。

文件:src/main/resources/db.properties

# Maven 打包时会自动替换 ${db.url}
jdbc.connection.url= ${db.url}
log.level=${log.level}
app.version=${project.version}
build.time=${maven.build.timestamp}

3. 在 .yaml / .yml 文件中使用 (Spring Boot 专用)

⚠️ 巨坑预警
在 Spring Boot 项目中,application.yml 也会使用 ${...} 来读取 Spring 配置。如果 Maven 也用 ${...},两者会冲突,导致报错或替换失败。

企业级解法
使用 Spring Boot 推荐的 @...@ 语法包裹变量名。

文件:src/main/resources/application.yml

spring:
  datasource:
    # 使用 @ 包裹,Maven 会识别并替换
    url: @db.url@
    username: admin
  
logging:
  level:
    root: @log.level@

info:
  # 注入项目信息
  app-name: ${project.artifactId} # 这里Maven也能解析标准变量,但在yml中推荐统一用@
  version: @project.version@

效果演示

假设执行命令:
mvn clean package -Pprod

1. 生成的 db.properties 内容:

jdbc.connection.url=jdbc:mysql://192.168.0.100:3306/prod_db
app.version=1.0.0-SNAPSHOT
...

2. 生成的 application.yml 内容:

spring:
  datasource:
    url: jdbc:mysql://192.168.0.100:3306/prod_db
...
logging:
  level:
    root: INFO

形态篇:交付产物控制

场景 3:可执行包形态切换 (Jar / Fat-Jar / War)

示例A: jar/fat-jar 多种打包方式

同一个项目,有时候需要作为依赖库(Lib-Jar,瘦包)被引用,有时候需要独立部署(Fat-Jar,全量包)。
以下配置演示了如何通过 Profile 配合 maven-shade-pluginmaven-dependency-plugin 实现无缝切换。

<profiles>
    <!-- 模式一:打胖包 (包含所有依赖的单文件) -->
    <profile>
        <id>fat-jar</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-shade-plugin</artifactId>
                    <version>3.6.0</version> 
                    <executions>
                        <execution>
                            <phase>package</phase> 
                            <goals><goal>shade</goal></goals>
                            <configuration>
                                <finalName>encode-fat</finalName>
                                <transformers>
                                    <!-- 自动在 MANIFEST.MF 中生成 Main-Class -->
                                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                        <mainClass>com.alianga.tools.Main</mainClass> 
                                    </transformer>
                                </transformers>
                                <!-- 关键:解决依赖冲突,排除签名文件 -->
                                <filters>
                                    <filter>
                                        <artifact>*:*</artifact>
                                        <excludes>
                                            <exclude>META-INF/*.SF</exclude>
                                            <exclude>META-INF/*.DSA</exclude>
                                            <exclude>META-INF/*.RSA</exclude>
                                        </excludes>
                                    </filter>
                                </filters>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>

    <!-- 模式二:打瘦包 (依赖分离到 lib 目录) -->
    <profile>
        <id>lib-jar</id>
        <build>
            <finalName>encode</finalName>
            <plugins>
                <!-- 1. 复制依赖到 lib -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>copy</id>
                            <phase>package</phase>
                            <goals><goal>copy-dependencies</goal></goals>
                            <configuration>
                                <outputDirectory>${project.build.directory}/lib</outputDirectory>
                                <includeScope>runtime</includeScope>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <!-- 2. 修改 Manifest Class-Path -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <configuration>
                        <finalName>encode</finalName>
                        <archive>
                            <manifest>
                                <mainClass>com.alianga.tools.Main</mainClass>
                                <addClasspath>true</addClasspath>
                                <classpathPrefix>lib/</classpathPrefix>
                                <useUniqueVersions>false</useUniqueVersions>
                            </manifest>
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

示例 B:Jar vs War (通过属性控制)

注意:虽然 Maven 支持用属性控制 ,但某些 IDE 可能需要在切换 Profile 后 "Reload Project" 才能识别结构变化。

<properties>
    <packaging.type>jar</packaging.type> <!-- 默认值 -->
</properties>

<packaging>${packaging.type}</packaging> <!-- 动态引用 -->

<profiles>
    <profile>
        <id>deploy-war</id>
        <properties>
            <packaging.type>war</packaging.type>
        </properties>
        <dependencies>
            <!-- 打 War 包时需要移除内置 Tomcat -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </profile>
</profiles>

场景 4:差异化构建 (社区版 vs 企业版)

软件产品常有“社区版”和“企业版”之分。
企业级做法:利用 Profile 控制 <modules> 聚合,一次构建产出不同功能的包。

<profiles>
    <profile>
        <id>community</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <modules>
            <module>core-engine</module>
            <module>web-ui</module>
        </modules>
    </profile>
    
    <profile>
        <id>enterprise</id>
        <modules>
            <module>core-engine</module>
            <module>web-ui</module>
            <module>security-audit-plugin</module> <!-- 企业版独有模块 -->
            <module>high-availability-cluster</module> <!-- 企业版独有模块 -->
        </modules>
        <dependencies>
            <!-- 企业版引入加密狗或License验证库 -->
            <dependency>
                <groupId>com.mycorp</groupId>
                <artifactId>license-validator</artifactId>
                <version>2.0.0</version>
            </dependency>
        </dependencies>
    </profile>
</profiles>
  • 效果:开发人员平时用社区版开发核心功能,构建服务器执行 mvn clean install -Penterprise ;发布时,构建服务器执行 mvn clean deploy -Penterprise 即可产出包含所有高级功能的全量包。

场景 5:云原生构建 (Docker 镜像)

在微服务架构中,有时候只需要打包 Jar 文件,有时候需要直接构建并推送 Docker 镜像。

企业级做法:
将 Docker 构建插件(如 docker-maven-plugin 或 jib-maven-plugin)放入特定的 Profile 中。[1]

配置示例:

<profiles>
    <profile>
        <id>docker-build</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>com.google.cloud.tools</groupId>
                    <artifactId>jib-maven-plugin</artifactId>
                    <version>3.3.1</version>
                    <configuration>
                        <to>
                            <image>myregistry.com/my-app:${project.version}</image>
                        </to>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>build</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

效果

  • 开发本地调试:mvn package (只生成 Jar,速度快)。
  • CI流水线构建镜像:mvn package -Pdocker-build (生成 Jar 并推送到私有镜像仓库)。

兼容篇:跨平台与架构适配

场景 6:跨平台/架构兼容 (Windows vs Linux/Mac)

痛点:团队里有人用 Windows,CI 服务器是 Linux。如果要在构建时执行 Shell 脚本,脚本格式(.bat vs .sh)不通会导致报错。
解法:利用 OS 探测功能,自动执行对应脚本。

示例A:

生成git 提交记录日志文件,不同操作系统,脚本语法不一致

POM 配置:

<profiles>
    <profile>
        <id>linux-or-mac</id>
        <activation>
            <os><family>unix</family></os>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>3.1.0</version>
                    <executions>
                        <execution>
                            <phase>clean</phase>
                            <goals><goal>exec</goal></goals>
                            <configuration>
                                <executable>${project.basedir}/git-log.sh</executable>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>windows</id>
        <activation>
            <os><family>windows</family></os>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>3.1.0</version>
                    <executions>
                        <execution>
                            <phase>clean</phase>
                            <goals><goal>exec</goal></goals>
                            <configuration>
                                <executable>${project.basedir}/git-log.bat</executable>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

配套脚本(根目录):

1. git-log.bat (Windows)

@echo off
@chcp 65001
set "firstLine=^版本号,作者,日期,提交信息"
echo %firstLine% > git.log
git --no-pager log  --date=iso --pretty="format:%%H , %%an , %%ad , %%s %%N" >> git.log
echo Git 日志已输出到 %LogFile% 文件

2. git-log.sh (Linux/Mac)

#!/bin/bash
cur_dir=$(cd "$(dirname "$0")"; pwd)
cd $cur_dir
echo "当前所在目录: $cur_dir"
currentBranch=$(git rev-parse --abbrev-ref HEAD)
echo "当前分支: $currentBranch" > git.log
firstLine="版本号,作者,日期,提交信息"
echo "$firstLine" >> git.log
git --no-pager log --date=iso --pretty=format:"%H , %an , %ad , %s %N" >> git.log
echo "Git 日志已输出到 $(pwd)/git.log 文件"
重要提示:权限管理

在 Linux 或 Mac 环境下,Maven 插件调用 Shell 脚本时,可能会因为文件没有“可执行权限(x)”而报错 Permission denied。

解决方案

  1. 本地开发时:手动授权 chmod +x git-log.sh

  2. Git 提交时:保留权限位(推荐)
    Git 会记录文件的权限位。如果你在 Windows 上提交 .sh 文件,Git 可能会丢失执行权限。建议使用以下命令强制将文件在 Git 索引中标记为可执行:

    git update-index --chmod=+x git-log.sh
    

    这样其他同事拉取代码或 CI 服务器构建时,该文件自动拥有执行权限。

示例B:

有些项目依赖本地库(JNI/JNA)或特定平台的二进制工具(如 Protobuf 编译器、Docker 客户端)。开发人员可能使用 Windows 或 Mac,但生产环境是 Linux。

企业级做法:
使用 Profile 的 activation 自动检测操作系统,加载对应的依赖或指定工具路径。

配置示例:

<profiles>
    <profile>
        <id>win-env</id>
        <activation>
            <os>
                <family>windows</family>
            </os>
        </activation>
        <properties>
            <protoc.path>C:\tools\protoc.exe</protoc.path>
            <native.lib.classifier>win-x64</native.lib.classifier>
        </properties>
    </profile>
    <profile>
        <id>linux-env</id>
        <activation>
            <os>
                <family>unix</family>
            </os>
        </activation>
        <properties>
            <protoc.path>/usr/bin/protoc</protoc.path>
            <native.lib.classifier>linux-x64</native.lib.classifier>
        </properties>
    </profile>
</profiles>

<dependencies>
    <!-- 根据 OS 自动加载对应的本地库 -->
    <dependency>
        <groupId>org.rocksdb</groupId>
        <artifactId>rocksdbjni</artifactId>
        <version>6.29.4</version>
        <classifier>${native.lib.classifier}</classifier>
    </dependency>
</dependencies>
  • 效果:Windows 同事和使用 Linux 的 Jenkins 都不需要修改任何配置,Maven 自动识别操作系统并下载对应的依赖包。

场景 7:依赖差异化 (JDK / 中间件版本)

场景:公司做架构升级,需要同时维护 JDK 8 和 JDK 17 版本,或者同时适配 Spark 2.4 和 3.0。

<properties>
    <!-- 默认配置 (例如 JDK 8 + Spark 2.4) -->
    <java.version>1.8</java.version>
    <spark.version>2.4.8</spark.version>
    <scala.binary.version>2.11</scala.binary.version>
</properties>

<profiles>
    <!-- 升级版配置 (JDK 17 + Spark 3.2) -->
    <profile>
        <id>jdk17</id>
        <properties>
            <java.version>17</java.version>
            <spark.version>3.2.1</spark.version>
            <scala.binary.version>2.12</scala.binary.version>
        </properties>
        <dependencies>
            <!-- 仅在 JDK 17 下需要的额外模块(如 javax 替换包) -->
            <dependency>
                <groupId>jakarta.xml.bind</groupId>
                <artifactId>jakarta.xml.bind-api</artifactId>
                <version>2.3.3</version>
            </dependency>
        </dependencies>
    </profile>
</profiles>

<dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
    </plugins>
</build>

质量与流水线篇

场景 8:CI/CD 流水线优化 (跳过繁重任务)

  • 在 CI/CD 流水线中,不同的阶段关注点不同。

    • 提交构建(Commit Build):要求快,做简单的单元测试。
    • 夜间构建(Nightly Build):可以慢,需要做全量集成测试、静态代码扫描(Sonar)、生成站点文档。
    • 发布构建(Release Build):需要做 GPG 签名、生成 Source Jar 和 Javadoc。

    企业级做法:
    通过 Profile 隔离耗时插件,避免每次本地构建都运行耗时任务。

    配置示例:

<profiles>
    <!-- CI 环境专用 Profile -->
    <profile>
        <id>ci-check</id>
        <build>
            <plugins>
                <!-- 仅在 CI 环境运行静态代码检查 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-checkstyle-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>validate</phase>
                            <goals><goal>check</goal></goals>
                        </execution>
                    </executions>
                </plugin>
                <!-- 仅在 CI 环境运行集成测试 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-failsafe-plugin</artifactId>
                    <executions>
                        <execution>
                            <goals><goal>integration-test</goal></goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>

    <!-- 发布专用 Profile -->
    <profile>
        <id>release</id>
        <build>
            <plugins>
                <!-- 发布时自动进行 GPG 签名 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-gpg-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>verify</phase>
                            <goals><goal>sign</goal></goals>
                        </execution>
                    </executions>
                </plugin>
                <!-- 发布时生成 JavaDoc -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-javadoc-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals><goal>jar</goal></goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

场景 9:插件级差异 (开发飞快 vs 生产合规)

企业最容易忽略的高级用法

痛点:OWASP Dependency Check 或 SpotBugs 运行非常慢(可能需要几分钟)。如果绑定在默认生命周期中,开发人员每次 mvn install 都要等很久,严重影响效率。但如果不加,预发布或生产环境又存在安全隐患。

企业级做法
开发环境(Dev)跳过重型检查,CI/CD 流水线的特定阶段(如 Release 或 Nightly Check)强制开启并配置“失败中断”。

<profiles>
    <!-- 严格检查模式:通常在 CI 服务器上激活 -->
    <profile>
        <id>strict-audit</id>
        <build>
            <plugins>
                <!-- 1. 代码风格检查 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-checkstyle-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>validate</phase>
                            <goals><goal>check</goal></goals>
                            <configuration>
                                <configLocation>company-checkstyle.xml</configLocation>
                                <failOnViolation>true</failOnViolation> <!-- 违规即构建失败 -->
                            </configuration>
                        </execution>
                    </executions>
                </plugin>

                <!-- 2. 静态潜在 Bug 扫描 (SpotBugs) -->
                <plugin>
                    <groupId>com.github.spotbugs</groupId>
                    <artifactId>spotbugs-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>verify</phase>
                            <goals><goal>check</goal></goals>
                        </execution>
                    </executions>
                </plugin>

                <!-- 3. 第三方依赖漏洞扫描 (OWASP - 极慢,必须隔离) -->
                <plugin>
                    <groupId>org.owasp</groupId>
                    <artifactId>dependency-check-maven</artifactId>
                    <executions>
                        <execution>
                            <phase>verify</phase>
                            <goals><goal>check</goal></goals>
                            <configuration>
                                <failBuildOnCVSS>7</failBuildOnCVSS> <!-- CVSS 分数大于7(高危)则构建失败 -->
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>
  • 用法:开发平时运行 mvn clean install (飞快);CI 流水线运行 mvn clean install -Pstrict-audit (严格把关)。

总结

Maven Profile 不仅仅是简单的配置切换,它是工程化、标准化、自动化的基石。
通过上述 9 大场景,我们实现了:

  1. 环境隔离:开发、测试、生产互不干扰。
  2. 形态控制:灵活产出 Fat-Jar、Lib-Jar 或 Docker 镜像。
  3. 流程分级:平衡本地开发的“快”与线上发布的“稳”。
  4. 平台兼容:抹平 Windows 与 Linux 的脚本差异。

掌握这些技巧,你的 pom.xml 就不再是简单的依赖列表,而是强大的构建脚本


整理不易,如果觉得有用,点个推荐喜欢支持一下!

Q.E.D.


寻门而入,破门而出