标准目录布局
本章承接"约定优于配置",将抽象的哲学落地为具体的目录结构。理解 Maven 的标准目录布局,是正确放置代码、资源、测试和构建产物的先决条件,也是团队协作时"无需解释"的默契基础。
核心机制
Maven 为项目定义了一套默认的目录结构,使得任何熟悉 Maven 的开发者都能立即知道去哪里找源代码、测试、资源和构建输出。
这句话的潜台词是:目录结构本身就是一种"接口"。当你说"这是一个 Maven 项目",就等于承诺了"源代码在 src/main/java,测试在 src/test/java,构建产物在 target/"。任何懂 Maven 的人不需要阅读你的 README 就能定位一切。
标准目录结构全貌
Maven 的目录约定不是随意制定的,它反映了 Java 项目从开发到交付的完整生命周期:
my-project/
├── pom.xml # 项目核心配置
├── src/
│ ├── main/
│ │ ├── java/ # 主源代码(.java 文件)
│ │ ├── resources/ # 主资源文件(配置文件、静态资源)
│ │ ├── filters/ # 资源过滤配置文件
│ │ ├── webapp/ # Web 项目专属(WEB-INF、静态页面)
│ │ └── assembly/ # 装配描述符(高级)
│ ├── test/
│ │ ├── java/ # 测试源代码
│ │ ├── resources/ # 测试资源文件
│ │ └── filters/ # 测试资源过滤配置
│ └── site/ # 项目文档站点源文件
├── target/ # 构建输出目录(自动生成)
│ ├── classes/ # 编译后的主类文件
│ ├── test-classes/ # 编译后的测试类文件
│ ├── surefire-reports/ # 测试报告
│ ├── my-project-1.0.0.jar # 打包产物
│ └── generated-sources/ # 插件生成的源代码
└── .mvn/ # Maven 包装器配置(可选)
目录用途对照表
| 目录路径 | 存放内容 | 生命周期阶段 | 是否参与打包 |
|---|---|---|---|
src/main/java | 主业务逻辑源代码 | compile | 是 |
src/main/resources | 主配置文件(Spring、MyBatis、日志配置等) | process-resources | 是 |
src/main/webapp | Web 应用静态资源(HTML、JS、WEB-INF) | package | 是(WAR) |
src/test/java | 单元测试、集成测试代码 | test-compile | 否 |
src/test/resources | 测试专用配置(如 H2 内存数据库配置) | process-test-resources | 否 |
target/classes | 编译后的 .class 文件和复制后的资源 | compile | 中间产物 |
target/test-classes | 编译后的测试类 | test-compile | 中间产物 |
target/surefire-reports | 测试执行报告(XML/HTML) | test | 否 |
target/*.jar / *.war | 最终打包产物 | package | 最终交付物 |
约定优于配置的体现
Maven 的目录约定直接决定了生命周期的默认行为:
mvn compile默认编译src/main/java→ 输出到target/classesmvn test-compile默认编译src/test/java→ 输出到target/test-classesmvn test默认运行target/test-classes中的 JUnit 测试mvn package默认将target/classes和src/main/resources打入 JAR/WAR
你不需要在 pom.xml 里写"编译哪个目录",因为 Maven 已经知道。
与 Ant 时代自定义目录的对比
| 维度 | Ant 时代(自定义目录) | Maven 时代(标准目录) |
|---|---|---|
| 目录命名 | 各项目不同:source/、code/、java/ | 统一:src/main/java |
| 构建脚本 | 每个 build.xml 都要声明源码路径 | pom.xml 无需声明 |
| 新成员上手 | 需要阅读文档或询问"代码放哪" | 一眼就知道 |
| 工具兼容性 | IDE、CI 工具需要单独配置路径 | 开箱即用 |
| 插件兼容性 | 自定义路径导致插件找不到文件 | 标准路径,插件默认生效 |
生活类比:图书馆的图书分类法
想象你走进一座图书馆:
- Ant 时代的图书馆:每座图书馆都有自己的分类法。A 馆把小说放在一楼,B 馆把小说放在三楼;A 馆用"作者名"排序,B 馆用"出版年份"排序。你每去一座新馆,都要先花半小时学习它的规则。
- Maven 时代的图书馆:全球统一——文学类在 A 区,科技类在 B 区,期刊在 C 区,每区内部按"中图法"编号排列。你只需学习一次,到了任何城市的图书馆都能直接找到书。
Maven 的标准目录就是这套"全球统一的图书分类法"。
图示
上图展示了 Maven 目录结构的数据流向:源代码层的内容经过编译、测试、打包三个阶段,最终流向构建输出层的产物。这个流向是固定的、可预期的,不需要任何额外配置。
完整示例
场景
飞翔科技正在开发一个员工管理系统 employee-system。CTO 大翔要求项目必须严格遵循 Maven 标准目录,方便后续微服务拆分。架构师白歌负责搭建项目骨架,后端小崔负责业务代码,前端黄俪负责页面资源,运维李眉负责部署脚本。
操作前:混乱的目录实验
白歌先做了一个"反例实验",故意打破约定,让小崔体验后果:
employee-system-bad/
├── pom.xml
├── code/ # ❌ 自定义:不叫 src,叫 code
│ ├── src/ # 主代码
│ │ └── com/feixiang/...
│ ├── config/ # ❌ 自定义:不叫 resources,叫 config
│ │ └── application.yml
│ └── test/ # ❌ 自定义:测试和主代码混在一起
│ └── com/feixiang/...
├── web/ # ❌ 自定义:静态资源放在 web/
│ └── index.html
└── output/ # ❌ 自定义:构建输出叫 output
对应的 pom.xml 被迫写满自定义路径:
<build>
<sourceDirectory>code/src</sourceDirectory>
<testSourceDirectory>code/test</testSourceDirectory>
<resources>
<resource>
<directory>code/config</directory>
</resource>
</resources>
<directory>output</directory>
</build>
后果:
- 小崔从其他 Maven 项目转过来,找不到
src/main/java,一脸茫然 - 黄俪的 HTML 文件放在
web/,mvn package时没有被自动打入 WAR,需要额外配置 - 李眉写 Jenkins 脚本时,发现构建产物不在
target/,而是散落在output/,脚本失效 maven-javadoc-plugin默认去src/main/java找代码,找不到,构建报错
操作后:标准目录布局
白歌重新创建标准结构:
employee-system/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/ # ✅ 小崔的业务代码
│ │ │ └── com/feixiang/employee/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ └── dao/
│ │ ├── resources/ # ✅ 配置文件
│ │ │ ├── application.yml
│ │ │ └── logback.xml
│ │ └── webapp/ # ✅ 黄俪的前端页面(Web 项目)
│ │ ├── WEB-INF/
│ │ └── index.html
│ └── test/
│ ├── java/ # ✅ 单元测试
│ │ └── com/feixiang/employee/
│ │ └── EmployeeServiceTest.java
│ └── resources/ # ✅ 测试专用配置
│ └── application-test.yml
└── target/ # ✅ 自动生成,李眉的部署脚本直接取这里
├── classes/
├── test-classes/
├── surefire-reports/
└── employee-system-1.0.0.war
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>
<groupId>com.feixiang</groupId>
<artifactId>employee-system</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
变化分析:
- 小崔入职第一天,看到
src/main/java就知道业务代码在哪,看到src/test/java就知道测试在哪 - 黄俪的
webapp/内容被maven-war-plugin自动打包进 WAR,无需额外配置 - 李眉的 Jenkins 脚本直接取
target/employee-system-1.0.0.war,脚本通用于所有 Maven 项目 mvn javadoc:javadoc自动找到src/main/java,生成 API 文档
易错点与常见问题
误区一:target/ 目录应该提交到版本控制
错误认知:"target/ 里有构建产物,我应该把它提交到 Git,方便同事直接下载。"
纠正:target/ 是纯生成目录,里面的内容全部由 Maven 从 src/ 和 pom.xml 推导生成。提交它会导致:
- 仓库体积膨胀(JAR 文件通常几十 MB)
- 合并冲突频发(
.class文件是二进制,Git 无法智能合并) - 不同环境产物混杂(Windows 和 Linux 编译的
.class可能有差异)
正确做法:在 .gitignore 中写入 target/。
误区二:src/main/resources 和 src/test/resources 可以混用
错误认知:"我把所有配置文件放在 src/main/resources,测试时也能读到,省事。"
纠正:src/test/resources 中的文件只在测试时可见,且会覆盖 src/main/resources 中的同名文件。这是隔离测试环境的关键机制。例如:
src/main/resources/application.yml配置生产数据库 MySQLsrc/test/resources/application.yml配置内存数据库 H2
如果混用,测试可能意外连接生产数据库,导致数据污染。
误区三:标准目录只适用于简单项目
错误认知:"我的项目有 50 个模块,标准目录不够用,必须自定义。"
纠正:Maven 的多模块项目(Multi-Module Project)正是通过每个模块内部遵循标准目录来实现统一的。父项目只负责聚合,子模块各自拥有完整的 src/main/java、src/test/java 和 target/。50 个模块意味着 50 套标准目录,而不是 50 套自定义目录。这是 Maven 管理大型代码库的核心能力。
小结
Maven 的标准目录布局是"约定优于配置"最直接的体现。它将源代码、资源、测试、构建产物固定在可预期的位置,使得任何熟悉 Maven 的开发者都能零成本上手新项目。与 Ant 时代的自定义目录相比,标准目录带来了团队协作效率、工具链兼容性和插件开箱即用的三重收益。
本章与全局的关系:本章回答了"代码应该放在哪里"。下一章"依赖机制"将回答"项目需要什么外部库、如何声明它们"——这是 pom.xml 最核心的功能之一。