CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Java 语言基础/课时/Maven 与项目结构
RawBlame
课时目录14 节

Java 语言基础

  1. 01Java 与 JVM 是什么
  2. 02基本语法与 main
  3. 03基本类型与引用类型
  4. 04条件与循环
  5. 05方法与参数
  6. 06数组
  7. 07类与封装
  8. 08继承接口与多态
  9. 09集合框架入门
  10. 10字符串
  11. 11异常处理
  12. 12文件 IO 入门
  13. 13Maven 与项目结构
  14. 14综合练习与 dsa-java 分工

Maven 与项目结构

pom.xml、backend 模块对照。

35 分钟

Maven 与项目结构

这一课你将能做到

  1. 读懂 pom.xml:坐标(GAV)、依赖、properties、build 插件各部分的作用
  2. 独立执行 Maven 常用命令(clean/compile/package),并说出标准目录结构每一层的职责
  3. 用 Spring Initializr 生成一个含 Web + MySQL + Redis 依赖的 Spring Boot 骨架项目,与之前的 javac 手工方式对比出「构建工具」的价值

为什么要学

前 13 课你一直用「单文件 + java Xxx.java」跑代码。写练习够了,但真实工程不行:本站后端依赖 Spring Boot、MyBatis、MySQL 驱动、Redis 客户端等几十个第三方 jar,手工下载、管理版本、维护 classpath 完全不现实。

Maven 解决三件事:

  1. 依赖管理:在 pom.xml 里写一行坐标,自动下载全部 jar(含传递依赖)
  2. 标准化构建:一套命令完成 编译 → 测试 → 打包,所有 Java 团队的工程结构长得一样
  3. 统一结构:进任何一家公司,src/main/java、src/main/resources 这些路径都是肌肉记忆

学完这课,你就有能力打开本站后端源码并看懂工程结构了。

先建立图像

Maven 的工作流:

pom.xml(项目说明书)
   │  mvn compile
   ▼
按坐标到本地仓库(~/.m2)找 jar ──没有──▶ 到远程仓库(阿里云镜像)下载
   │
   ▼
编译 src/main/java ──▶ target/classes
   │  mvn package
   ▼
打成 target/xxx.jar(可部署产物)

坐标定位一个依赖,全球唯一:
  groupId    谁写的        com.mysql
  artifactId 叫什么        mysql-connector-j
  version    哪个版本      8.4.0

跟着做

步骤 1:确认 Maven 可用

mvn -v

期望输出包含 Apache Maven 3.9.x 和 Java version: 17+。

没有的话:IDEA 自带捆绑版 Maven(Settings → Build Tools → Maven 可查路径);独立安装则解压后把 bin 加入 PATH,并配置阿里云镜像加速(settings.xml 的 <mirrors> 节点里加 https://maven.aliyun.com/repository/public,国内下载速度差 10 倍以上)。

步骤 2:手写一个最小 Maven 工程

新建目录结构(手工建目录体验一次,以后都让 IDE 生成):

maven-demo/
├── pom.xml
└── src/
    └── main/
        ├── java/
        │   └── com/roadmap/demo/App.java
        └── resources/
            └── app.properties

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>

    <!-- 坐标:本项目在全球 Maven 世界的唯一身份证 -->
    <groupId>com.roadmap</groupId>
    <artifactId>maven-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>          <!-- 打包类型:jar(库/应用)、war(传统 Web) -->

    <properties>
        <maven.compiler.source>17</maven.compiler.source>   <!-- 源码语法级别 -->
        <maven.compiler.target>17</maven.compiler.target>   <!-- 编译目标字节码版本 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <!-- 依赖清单:写一行坐标,Maven 自动下载并纳入 classpath -->
    <dependencies>
        <!-- 必备:给所有未标注 scope 的依赖提供的默认(compile)范围示例 -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.11.0</version>
        </dependency>
        <!-- 只在编译期需要、运行时由容器提供:servlet API 常设 provided -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

App.java:

package com.roadmap.demo;

import com.google.gson.Gson;
import java.util.Map;

public class App {
    public static void main(String[] args) {
        // 引用刚下载的第三方库 Gson——依赖管理生效的直接证据
        String json = new Gson().toJson(Map.of("user", "tom", "id", 1001));
        System.out.println("JSON: " + json);
        System.out.println("配置读取: " + System.getProperty("app.name", "默认值"));
    }
}

src/main/resources/app.properties 先放任意内容(此步仅占位,resources 会进最终 jar)。

步骤 3:执行构建生命周期

在 maven-demo 目录(pom.xml 所在处)执行:

mvn clean compile
# clean:删 target;compile:编译 src/main/java → target/classes
# 第一次运行会下载大量依赖到 ~/.m2,耐心等待

mvn package
# 编译 + 测试 + 打包 → target/maven-demo-1.0.0.jar
# 注意:普通 jar 不能直接 java -jar 运行(没有 Main-Class 元数据),见卡点急救

mvn dependency:tree
# 打印依赖树:谁引入了谁、版本冲突在哪里,排冲突神器

对比一下:没有 Maven 之前,你得手动下载 gson.jar、手动 javac -cp gson.jar ...、手动维护一串 classpath;现在一切自动化。

步骤 4:scope 依赖范围

<dependencies>
    <!-- compile(默认):编译+测试+运行都可用,会传递给下游 -->
    <!-- provided:编译期可用,运行期由环境提供(如 servlet-api、lombok) -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>            <!-- 只参与 src/test 的编译运行,不进最终 jar -->
    </dependency>
    <!-- runtime:编译不可见,运行期可用。典型:MySQL 驱动(代码只写 JDBC 接口,不写驱动类名) -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.4.0</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

选错 scope 的典型症状:编译报「程序包不存在」(该 runtime 的写成 test),或启动报「找不到驱动类」。

步骤 5:用 Spring Initializr 生成本站同款骨架

打开 start.spring.io(IDEA 里 New Project → Spring Initializr 同款):

  • Project:Maven,Language:Java,Spring Boot:3.x
  • Group:com.roadmap,Artifact:backend
  • 勾选依赖:Spring Web、Spring Data JPA(或 MyBatis Framework)、MySQL Driver、Lombok、Data Redis

生成解压后,得到的关键结构:

backend/
├── pom.xml                        ← 依赖里出现 spring-boot-starter-web 等
└── src/
    ├── main/
    │   ├── java/com/roadmap/backend/
    │   │   └── BackendApplication.java   ← @SpringBootApplication + main
    │   └── resources/
    │       ├── application.yml           ← 端口、MySQL 连接、Redis 地址全在这
    │       ├── static/                   ← 静态资源
    │       └── templates/                ← 模板
    └── test/java/                        ← 测试代码(package 阶段自动执行)

pom.xml 的核心差异——parent 统一管理版本(BOM 机制):

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
</parent>
<dependencies>
    <!-- starter = 一组配套依赖的打包;版本号由 parent 统一裁定,不用写 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

在 IDEA 中打开,等索引建完,点击运行 BackendApplication 即可启动内嵌 Tomcat——这就是你后面 Spring Boot 课程的起点。

步骤 6:对照本站后端真实结构

本站 backend 模块采用多模块聚合工程,骨架如下(概念对照用):

backend/
├── pom.xml                    ← 聚合 POM:modules 列出子模块,dependencyManagement 定版本
├── backend-common/            ← 工具类、统一响应体、全局异常(第 11 课的 BizException 在这)
├── backend-domain/            ← 实体、Mapper、Service(业务核心)
└── backend-web/               ← Controller、配置、启动类,打包成可执行 jar

多模块的好处:公共代码复用、依赖边界清晰。现在只需要认识这个形状,Spring 课会带你逐层填充。

讲清楚要点

  1. Maven = 依赖管理 + 构建生命周期 + 约定优于配置:目录位置、阶段名称都是约定,不配置也有合理默认值。
  2. 坐标(GAV)全球唯一:groupId 用域名倒写,artifactId 项目名,version 遵循语义化(1.0.0;SNAPSHOT 表示开发中快照)。
  3. 依赖会传递:A 依赖 B、B 依赖 C,A 自动拥有 C。传递带来的版本冲突按「最短路径优先、先声明优先」仲裁,排查靠 dependency:tree。
  4. scope 决定依赖出现在哪个阶段:compile 全程;provided 只编译;runtime 只运行;test 只测试。jar 最终产物里不含 test/provided。
  5. 生命周期是有序阶段链:mvn package 会自动执行之前的 validate→compile→test;clean 和 site 是独立链。日常就记 clean、compile、package 三个。
  6. starter 是 Spring 的依赖打包艺术:引一个 spring-boot-starter-web,自动带上 Spring MVC + Jackson + 内嵌 Tomcat + 日志全家桶,版本由 parent 兜底。

你可能会懵的地方

  • mvn package 出的 jar 为什么不能 java -jar 直接跑? 默认 jar 插件生成的「瘦 jar」不含依赖 jar,也没有 Main-Class 清单。Spring Boot 的 spring-boot-maven-plugin 会 repackage 成「胖 jar」——依赖全嵌进去、指定启动类。所以 Spring Boot 项目才能 java -jar backend.jar 一键启动。

  • ~/.m2 仓库是什么?删了会怎样? 本地缓存仓库,存所有下载过的 jar。删了不丢代码,下次构建重新联网下载(慢)。磁盘紧张时可删 *.lastUpdated 残留文件修复「下载失败缓存」。

  • 同一个 jar 两个版本被依赖了,听谁的? Maven 按最短路径 + 先声明优先仲裁,最终只留一个版本进 classpath。冲突排查:mvn dependency:tree -Dincludes= groupId:artifactId 定位谁引入的,用 <exclusions> 排掉或 <dependencyManagement> 钉死版本。

  • 为什么 Spring Boot 的依赖不用写版本号? parent(spring-boot-starter-parent)里的 <dependencyManagement> 预置了整个生态的版本清单,子项目只写坐标。既省事,又保证几十个 jar 版本互相兼容。

  • Maven 和 Gradle 什么关系? 两个主流构建工具,做同样的事。Maven 用 XML、约定重、国内企业存量最大;Gradle 用 Groovy/Kotlin DSL、灵活、Android 系主流。本站和本课程用 Maven。

  • IDEA 里点绿色三角运行,和 mvn 有什么关系? IDEA 的运行按钮直接调 javac/java(快,跳过完整生命周期);Maven 命令走标准构建链(打包、CI 部署必须)。开发期两者并存,交付靠 Maven。

错法对照

错法问题正解
手工往工程里塞 jar 包建 lib 目录版本失控、无法传递依赖全部走 pom 依赖坐标
依赖不写 version 且没有 parent/dependencyManagement构建报「missing version」写版本或交给 parent 管理
MySQL 驱动 scope 写成 compile 还纳进 war冲突/冗余驱动用 runtime;web 容器托管类用 provided
改了依赖 IDEA 不认索引未刷新点 Maven 面板刷新按钮,或 mvn -U 强制更新
mvn package 后 java -jar 失败「no main manifest」瘦 jar 无启动配置Spring Boot 项目确认 repackage 插件;普通项目配 mainClass
groupId 写成项目拼音缩写 com.xmgl不专业、易冲突域名倒写 com.roadmap.xxx

卡点急救

  1. 下载依赖卡住/超时:国内网络问题。settings.xml 配阿里云镜像;或走公司私服(Nexus)。检查是否被代理劫持。
  2. 报错「无效的目标发行版:17」:本机 Maven 用的 JDK 版本低于 17。IDEA 里 Project Structure → SDK 选 17+;Settings → Maven → JDK for importer 同步改。
  3. No compiler is provided in this environment:装的是 JRE 不是 JDK,或 JAVA_HOME 指错。指向 JDK 根目录。
  4. 依赖红色下划线「Cannot resolve」:先刷新 Maven 面板;再删 ~/.m2/repository 下对应目录重下;版本号拼写错误也常见(去 mvnrepository.com 核对)。
  5. resources 里的文件读取不到:确认放在 src/main/resources 且路径引用 /app.properties 根开头;注意 target/classes 才是运行时实际位置,改完 resources 要重新构建。
  6. 多模块工程子模块标红:先在父 pom 所在目录执行 mvn install,让子模块先装进本地仓库。

面试常问

  • Maven 的生命周期有哪些?常用命令? 三套独立链:default(核心:validate→compile→test→package→install→deploy)、clean、site。日常:clean compile、clean package、install(装进本地仓库供其他模块引用)。

  • Maven 如何解决依赖冲突? 传递依赖按「最短路径优先、声明顺序优先」仲裁;dependency:tree 定位来源;exclusions 排除、dependencyManagement 统一版本。

  • compile、provided、runtime、test 四种 scope 的区别? 参与的构建阶段与是否打进产物:compile 全程且传递;provided 编译期在、运行期由容器提供;runtime 编译不可见运行可用(典型 JDBC 驱动);test 仅测试期(典型 JUnit)。

  • dependencyManagement 和 dependencies 的区别? dependencyManagement 只声明版本不实际引入,子模块引用时免写版本;dependencies 实际引入依赖。BOM 统一版本治理就靠它。

  • Spring Boot 的 starter 是什么机制? 依赖聚合 + 自动配置:一个 starter 拉齐一组互相兼容的依赖,配合 @EnableAutoConfiguration 按类路径自动装配 Bean。相当于「功能级依赖包」。

自测三问

  1. GAV 坐标分别是什么?为什么 groupId 惯例用域名倒写?
  2. mvn package 生成的 jar 想用 java -jar 直接运行,缺了什么?Spring Boot 怎么补上的?
  3. MySQL 驱动应该用什么 scope?为什么?写错成 test 会在哪个阶段报什么错?

小结一句话

Maven 用坐标管理依赖、用标准生命周期构建、用约定目录统一工程;starter + parent 是 Spring Boot 依赖体系的两大支柱——从此告别手工 javac,你具备了打开本站后端源码的能力。

延伸

  • Maven 官方入门
  • Spring Initializr
  • 下一课:综合练习——把 13 课知识拧成一个可运行的「用户管理小系统」,并划清与 dsa-java(算法路线)的分工。
加载本节练习…
← 上一课文件 IO 入门下一课 →综合练习与 dsa-java 分工