最佳实践
本章是 Maven 入门教程的收官之作。掌握最佳实践,意味着你不仅能"让项目跑起来",还能"让项目跑得稳、传得久"——这是 985 高校计算机专业学生应有的工程素养。
核心机制
当你遵循 Maven 的约定并善用其设计时,Maven 才能发挥最大价值。与 Maven 的默认值对抗,通常会导致更多配置、更多维护成本和更多团队困惑。
这句话是本章所有实践的哲学基础:不是 Maven 限制了你,而是它在引导你走向团队可维护的道路。
为什么需要最佳实践?
Maven 的灵活性是一把双刃剑。同一个目标可以用多种方式实现,但不同选择对长期维护的影响天差地别:
| 维度 | 遵循最佳实践 | 随意配置 |
|---|---|---|
| 新成员上手 | 1 天 | 1 周 |
| 依赖冲突频率 | 低 | 高 |
| CI/CD 兼容性 | 好 | 差 |
| 版本升级成本 | 低 | 高 |
| 跨项目复用 | 容易 | 困难 |
最佳实践的层次
Maven 最佳实践可分为四个层次:
- POM 设计层:版本管理、依赖声明、模块组织
- 构建配置层:插件配置、生命周期定制、Profile 使用
- 团队协作层:仓库策略、发布流程、文档规范
- 工程文化层:约定优先、配置最小化、可复现构建
图示
上图展示了从反模式到最佳实践的转变路径,以及遵循最佳实践带来的长期收益。本章的每一条实践,都是飞翔科技技术部在多次踩坑后沉淀的共识。
完整示例
场景
飞翔科技的 employee-system 项目从最初的单体应用,逐步演进为多模块项目。CTO 大翔要求架构师白歌制定一套 Maven 最佳实践规范,所有新项目必须遵循。
最佳实践清单表
| 序号 | 实践项 | 具体做法 | 违反后果 | 优先级 |
|---|---|---|---|---|
| 1 | 版本集中管理 | 父 POM 的 <dependencyManagement> 统一声明所有依赖版本 | 版本散落在各模块,升级时遗漏 | 🔴 高 |
| 2 | 属性化版本号 | 在 <properties> 中定义版本号,如 <spring-boot.version>3.2.0</spring-boot.version> | 版本号硬编码,批量替换易出错 | 🔴 高 |
| 3 | 按需选择 scope | compile / provided / runtime / test 根据实际使用场景选择 | 全部用 compile 导致产物臃肿、容器冲突 | 🔴 高 |
| 4 | 多模块合理拆分 | 按职责拆分:api / service / dao / web / common | 单模块代码量过大,编译慢、耦合高 | 🟡 中 |
| 5 | 统一编码和 JDK 版本 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 跨平台构建出现乱码或编译失败 | 🔴 高 |
| 6 | Profile 管理多环境 | dev / test / prod 三个 Profile,默认激活 dev | 维护多份 POM 或手动改配置 | 🟡 中 |
| 7 | 资源过滤外部化配置 | 数据库连接、日志路径等用 ${...} 占位符 | 配置硬编码在源码中,不同环境需改代码 | 🟡 中 |
| 8 | 敏感信息走环境变量 | 密码、密钥用 ${ENV_VAR} 注入,不提交到版本控制 | 密码泄露到 Git 仓库 | 🔴 高 |
| 9 | CI 构建强制 clean | Jenkins 流水线使用 mvn clean package | 旧产物干扰,构建不可复现 | 🔴 高 |
| 10 | 定期执行 dependency:analyze | 每月清理未使用依赖,补充缺失声明 | 依赖膨胀、构建变慢、冲突隐患 | 🟡 中 |
| 11 | SNAPSHOT 仅限开发阶段 | 发布到测试/生产环境前,版本改为 RELEASE | SNAPSHOT 每天检查更新,生产环境不稳定 | 🔴 高 |
| 12 | 插件版本显式声明 | 所有插件声明 <version>,不依赖 Maven 内置版本 | 不同 Maven 版本的默认插件版本不同 | 🟡 中 |
操作前:反模式示例
假设小崔在没有规范的情况下,写了一个"反面教材"式的 pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<!-- 反模式:没有 groupId 规范,随意命名 -->
<groupId>feixiang</groupId>
<artifactId>emp</artifactId>
<version>1.0</version>
<!-- 反模式:没有声明编码,Windows 默认 GBK -->
<dependencies>
<!-- 反模式:全部用 compile scope -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<!-- 反模式:servlet-api 用 compile,与 Tomcat 冲突 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 反模式:密码硬编码 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.3.0.0</version>
</dependency>
</dependencies>
<!-- 反模式:没有插件版本,依赖 Maven 默认 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
操作步骤
步骤一:应用父 POM 统一版本管理
白歌创建了公司级父 POM feixiang-parent:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!-- 版本集中管理 -->
<spring-boot.version>3.2.0</spring-boot.version>
<mybatis.version>3.5.14</mybatis.version>
<mybatis-spring.version>3.0.3</mybatis-spring.version>
<jackson.version>2.15.2</jackson.version>
<junit-jupiter.version>5.10.0</junit-jupiter.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
步骤二:子项目继承父 POM
小崔重构后的 employee-system/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.feixiang</groupId>
<artifactId>feixiang-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>employee-system</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<!-- 版本从父 POM 继承,无需写 version -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
<!-- 正确选择 scope:servlet-api 由容器提供 -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<db.host>localhost</db.host>
<db.password>${DEV_DB_PASSWORD}</db.password>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<db.host>prod-db.feixiang.com</db.host>
<db.password>${PROD_DB_PASSWORD}</db.password>
</properties>
</profile>
</profiles>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<!-- 版本从父 POM 继承 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
反模式对比
| 维度 | 反模式(重构前) | 最佳实践(重构后) |
|---|---|---|
| 版本管理 | 硬编码在各模块 | 父 POM 的 dependencyManagement 统一管理 |
| 编码设置 | 未声明,依赖平台默认 | 显式声明 UTF-8 |
| JDK 版本 | 未声明 | 父 POM 统一 source/target=17 |
| scope 选择 | 全部 compile | servlet-api 用 provided,junit 用 test |
| 敏感信息 | 密码可能硬编码 | 走环境变量 ${DEV_DB_PASSWORD} |
| 插件版本 | 未声明 | 父 POM pluginManagement 锁定 |
| 多环境 | 无 Profile | dev / prod Profile,默认激活 dev |
| 资源过滤 | 未开启 | <filtering>true</filtering> |
易错点与常见问题
误区一:最佳实践是"大公司才需要的东西"
错误认知:"我们团队只有 3 个人,不需要搞什么父 POM、版本锁定,太麻烦了。"
纠正:团队越小,规范越重要。大公司的规范有专人维护,小团队如果一开始不建立规范,三个月后回来看自己的代码都会困惑。飞翔科技的 feixiang-parent 最初也是为 5 人团队设计的,随着团队扩张,它的价值反而更加凸显——因为新成员可以"零成本"遵循既定规范。
误区二:parent POM 限制了灵活性
错误认知:"继承父 POM 后,我想升级一个依赖版本还得改父 POM,影响所有项目,不灵活。"
纠正:子项目可以覆盖父 POM 的版本。如果某个子项目需要特殊版本,直接在子项目的 <dependencies> 中声明 <version> 即可,它会覆盖父 POM 的 dependencyManagement。父 POM 提供的是"默认值",不是"强制值"。
<!-- 子项目覆盖父 POM 的版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.5</version> <!-- 覆盖父 POM 的 3.2.0 -->
</dependency>
误区三:最佳实践清单一旦制定就永不改变
错误认知:"白歌定的规范是圣旨,永远不变。"
纠正:最佳实践是活文档,应随技术演进定期评审。飞翔科技每季度召开一次"Maven 规范评审会",讨论:
- 是否有新的依赖版本需要升级
- 是否有不再维护的依赖需要替换
- 是否有更优的插件配置可以推广
- 团队在实践中遇到的新问题
误区四:多模块拆分越细越好
错误认知:"我把项目拆成 20 个模块,每个模块只有几百行代码,这样最规范。"
纠正:多模块拆分的粒度应遵循"高内聚、低耦合"原则,不是越细越好。过度拆分会导致:
- 构建时间增加(每个模块都有独立的 compile/test/package 开销)
- 版本管理复杂(模块间版本对齐成本上升)
- 发布流程繁琐(需要逐个 install/deploy)
飞翔科技的实践是:单体应用 5~8 个模块,微服务每个服务 1~3 个模块(api / service / starter)。
小结
Maven 最佳实践不是束缚,而是将团队经验沉淀为可复用的默认值。本章总结的 12 条实践覆盖了 POM 设计、构建配置、团队协作和工程文化四个层次,核心思想是:
- 版本集中管理:父 POM +
dependencyManagement+<properties> - scope 精准选择:
compile/provided/runtime/test各司其职 - 配置外部化:Profile + 资源过滤 + 环境变量,实现"同代码多环境"
- 构建可复现:
mvn clean package+ 锁定插件版本 + CI 强制测试 - 定期体检:
dependency:analyze清理冗余,保持依赖健康
本章与全局的关系:本章是 Maven 入门教程的终点,也是你 Maven 实践的新起点。从"理解机制"到"养成习惯",最佳实践将伴随你整个职业生涯。建议将本章的清单打印出来,贴在工位上,作为每次新建项目时的自检表。