约定优于配置
本章承接"Maven 是什么",深入讲解 Maven 的设计哲学。理解"约定优于配置"(Convention Over Configuration),是理解 Maven 标准目录结构、默认生命周期、隐式插件绑定等所有"默认行为"的钥匙。
核心机制
Maven 提倡约定优于配置,即系统、库和框架应该假定合理的默认值,从而减少对不必要配置的需求。
这句话的潜台词是:如果你按照 Maven 的"约定"做事,你可以写很少的 XML 配置;如果你非要打破约定,你就得写很多配置来解释你的例外。
什么是"约定"?什么是"配置"?
| 维度 | 约定(Convention) | 配置(Configuration) |
|---|---|---|
| 本质 | 行业/工具默认的"潜规则" | 显式声明的"规则说明" |
| 成本 | 学习一次,终身受益 | 每个项目都要写一遍 |
| 示例 | 源代码放在 src/main/java | 在 pom.xml 里写 <sourceDirectory> 指定别的路径 |
| 代价 | 新成员需要学习约定 | 新成员需要读懂你的自定义配置 |
Maven 中的"约定"体现在哪里?
Maven 的约定渗透在项目的方方面面:
目录结构约定
- 源代码必须放在
src/main/java - 资源文件必须放在
src/main/resources - 测试代码必须放在
src/test/java - 构建输出自动放到
target/
- 源代码必须放在
构建生命周期约定
compile阶段默认编译src/main/javatest阶段默认运行src/test/java里的 JUnit 测试package阶段默认把编译结果打成 JAR
插件绑定约定
compile阶段自动绑定maven-compiler-plugintest阶段自动绑定maven-surefire-pluginpackage阶段自动绑定maven-jar-plugin
坐标命名约定
groupId用组织域名的倒写(如com.feixiang)artifactId用项目/模块名(如employee-system)version用三段式版本号(如1.0.0)
生活类比:交通规则
想象一座城市的交通系统:
- "配置"模式的城市:每个路口都没有红绿灯,司机到达路口前必须停车查看一本厚厚的《路口通行规则手册》,确认自己是否有优先权。不同路口规则不同,新手司机天天出事故。
- "约定"模式的城市:全国统一"红灯停、绿灯行、右转让直行"。你只需学习一次这套约定,到了任何路口都知道该怎么做。偶尔有特殊需求的路口(如单行道、潮汐车道),才会额外立一块标志牌(配置)。
Maven 就是后者:它用一套行业共识的约定,消除了"每个项目都要重新解释规则"的重复劳动。
图示
上图展示了"约定优于配置"的核心价值:约定被所有项目共享,配置只需描述项目的独特之处。在 Ant 时代,每个项目的 build.xml 都是从头写的;在 Maven 时代,三个项目的 pom.xml 可能只有依赖列表不同,其余全部交给约定。
完整示例
场景
飞翔科技有两个项目:
- 项目 A:
employee-system(员工管理系统) - 项目 B:
payroll-service(薪资计算服务)
CTO 大翔要求所有项目结构统一,方便人员流动时快速上手。
反例:打破约定的代价
假设白歌不喜欢 src/main/java,想把源代码放在 code/src:
<project>
...
<build>
<sourceDirectory>code/src</sourceDirectory>
<testSourceDirectory>code/test</testSourceDirectory>
<outputDirectory>build/classes</outputDirectory>
<testOutputDirectory>build/test-classes</testOutputDirectory>
<resources>
<resource>
<directory>code/res</directory>
</resource>
</resources>
</build>
</project>
后果:
- 小崔从
employee-system调到payroll-service时,发现后者源代码在code/src,前者在src/main/java,瞬间混乱 - 李眉写自动化部署脚本时,不得不为每个项目单独判断源代码路径
- 使用标准 Maven 插件(如
maven-javadoc-plugin)时,插件默认去src/main/java找代码,找不到就报错,需要额外配置
正例:遵循约定的收益
两个项目都遵循 Maven 标准结构:
employee-system/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ └── resources/
│ └── test/
│ ├── java/
│ └── resources/
payroll-service/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ └── resources/
│ └── test/
│ ├── java/
│ └── resources/
两个 pom.xml 都无需声明目录路径,因为 Maven 已经知道去哪里找代码。
收益:
- 小崔第一天入职,看到目录结构就知道代码在哪、配置在哪、测试在哪
- 李眉写一个通用的 Jenkins 流水线脚本,适用于所有 Maven 项目
- 任何标准插件开箱即用,无需额外配置路径
易错点与常见问题
误区一:"约定优于配置"等于"不能配置"
错误认知:"Maven 强制我用 src/main/java,我想改都改不了。"
纠正:Maven 允许你打破约定,只是不推荐。如上例所示,你可以通过 <sourceDirectory> 等标签自定义路径。但每打破一个约定,就要写几行配置,而且团队成员都要学习你的"例外"。约定的价值在于"默认正确",而非"强制服从"。
误区二:IDE 自动生成目录就是"约定"
错误认知:"我用 IDEA 创建 Maven 项目,IDEA 帮我生成了目录,所以这是 IDEA 的约定。"
纠正:IDEA 生成的 src/main/java 不是 IDEA 的发明,而是Maven 的约定。IDEA 只是遵循了 Maven 的规范。如果你用命令行 mvn archetype:generate 创建项目,生成的目录结构和 IDEA 完全一致——这才是约定的力量:工具不同,结构相同。
误区三:约定只关乎目录结构
错误认知:"约定优于配置就是让我把代码放在固定目录里,别的没什么。"
纠正:目录结构只是最显眼的约定。更深层的约定包括:
- 生命周期阶段的默认顺序(validate → compile → test → package → install)
- 插件目标的默认绑定(
compile阶段默认用maven-compiler-plugin:compile) - 依赖范围的默认行为(不声明
scope就是compile) - 版本号的默认解析规则(SNAPSHOT 版本每天检查更新)
这些隐式约定构成了 Maven 的"自动运转"能力,你不需要在 pom.xml 里写"先编译再测试",因为生命周期约定已经帮你排好了。
小结
"约定优于配置"是 Maven 的设计哲学,它通过一套行业共识的默认值(目录结构、生命周期、插件绑定等),让开发者只需描述项目的"独特之处"(如依赖列表、版本号),而无需重复声明"通用规则"。遵循约定,项目配置精简、团队上手快速、工具链兼容性好;打破约定,配置膨胀、协作成本上升、插件兼容性下降。
本章与全局的关系:本章解释了"为什么 Maven 的目录结构和生命周期是固定的"。下一章"安装与配置"将带你亲手搭建 Maven 环境,验证这些约定如何在真实命令中体现。