乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 介绍与核心概念

    • Maven是什么
    • 约定优于配置
  • 第2章 安装与配置

    • 安装与验证
    • settings.xml
    • 本地仓库与镜像
  • 第3章 POM与项目坐标

    • POM
    • GAV坐标
    • packaging
  • 第4章 标准目录布局

    • 标准目录布局
  • 第5章 依赖机制

    • dependencies
    • scope
    • 依赖传递
    • 依赖冲突与调解
    • exclusions
    • optional
    • dependencyManagement
  • 第6章 仓库

    • 仓库体系
    • 本地仓库
    • 远程仓库与镜像
    • 私服
  • 第7章 构建生命周期

    • 生命周期概述
    • clean 生命周期
    • default 生命周期
    • site 生命周期
    • 生命周期与插件绑定
  • 第8章 插件

    • 插件概述
    • maven-compiler-plugin
    • maven-surefire-plugin
    • maven-war-plugin
  • 第9章 继承与聚合

    • parent继承
    • 聚合
    • BOM
    • properties
  • 第10章 属性与资源过滤

    • 资源过滤
    • Profile
  • 第11章 常用命令

    • mvn compile
    • mvn test
    • mvn package
    • mvn clean
    • mvn install
    • mvn dependency:tree
  • 第12章 常见问题与最佳实践

    • 依赖冲突排查
    • 最佳实践

最佳实践

本章是 Maven 入门教程的收官之作。掌握最佳实践,意味着你不仅能"让项目跑起来",还能"让项目跑得稳、传得久"——这是 985 高校计算机专业学生应有的工程素养。


核心机制

当你遵循 Maven 的约定并善用其设计时,Maven 才能发挥最大价值。与 Maven 的默认值对抗,通常会导致更多配置、更多维护成本和更多团队困惑。

这句话是本章所有实践的哲学基础:不是 Maven 限制了你,而是它在引导你走向团队可维护的道路。

为什么需要最佳实践?

Maven 的灵活性是一把双刃剑。同一个目标可以用多种方式实现,但不同选择对长期维护的影响天差地别:

维度遵循最佳实践随意配置
新成员上手1 天1 周
依赖冲突频率低高
CI/CD 兼容性好差
版本升级成本低高
跨项目复用容易困难

最佳实践的层次

Maven 最佳实践可分为四个层次:

  1. POM 设计层:版本管理、依赖声明、模块组织
  2. 构建配置层:插件配置、生命周期定制、Profile 使用
  3. 团队协作层:仓库策略、发布流程、文档规范
  4. 工程文化层:约定优先、配置最小化、可复现构建

图示

上图展示了从反模式到最佳实践的转变路径,以及遵循最佳实践带来的长期收益。本章的每一条实践,都是飞翔科技技术部在多次踩坑后沉淀的共识。


完整示例

场景

飞翔科技的 employee-system 项目从最初的单体应用,逐步演进为多模块项目。CTO 大翔要求架构师白歌制定一套 Maven 最佳实践规范,所有新项目必须遵循。

最佳实践清单表

序号实践项具体做法违反后果优先级
1版本集中管理父 POM 的 <dependencyManagement> 统一声明所有依赖版本版本散落在各模块,升级时遗漏🔴 高
2属性化版本号在 <properties> 中定义版本号,如 <spring-boot.version>3.2.0</spring-boot.version>版本号硬编码,批量替换易出错🔴 高
3按需选择 scopecompile / provided / runtime / test 根据实际使用场景选择全部用 compile 导致产物臃肿、容器冲突🔴 高
4多模块合理拆分按职责拆分:api / service / dao / web / common单模块代码量过大,编译慢、耦合高🟡 中
5统一编码和 JDK 版本<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>跨平台构建出现乱码或编译失败🔴 高
6Profile 管理多环境dev / test / prod 三个 Profile,默认激活 dev维护多份 POM 或手动改配置🟡 中
7资源过滤外部化配置数据库连接、日志路径等用 ${...} 占位符配置硬编码在源码中,不同环境需改代码🟡 中
8敏感信息走环境变量密码、密钥用 ${ENV_VAR} 注入,不提交到版本控制密码泄露到 Git 仓库🔴 高
9CI 构建强制 cleanJenkins 流水线使用 mvn clean package旧产物干扰,构建不可复现🔴 高
10定期执行 dependency:analyze每月清理未使用依赖,补充缺失声明依赖膨胀、构建变慢、冲突隐患🟡 中
11SNAPSHOT 仅限开发阶段发布到测试/生产环境前,版本改为 RELEASESNAPSHOT 每天检查更新,生产环境不稳定🔴 高
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 选择全部 compileservlet-api 用 provided,junit 用 test
敏感信息密码可能硬编码走环境变量 ${DEV_DB_PASSWORD}
插件版本未声明父 POM pluginManagement 锁定
多环境无 Profiledev / 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 实践的新起点。从"理解机制"到"养成习惯",最佳实践将伴随你整个职业生涯。建议将本章的清单打印出来,贴在工位上,作为每次新建项目时的自检表。

上一页
依赖冲突排查