Maven 必备¶
第 02 章你装好了 Maven,知道它是"Java 的 npm"。但 npm 用熟了不代表 Maven 就懂——它有些 npm 里根本没有的概念(生命周期、依赖范围、多模块聚合),恰恰是前端人最容易卡住的地方。这一章把它们讲透。
GAV 坐标:依赖的"身份证"¶
每个 Maven 依赖/项目靠三个字段唯一定位,叫 GAV:
| 字段 | 含义 | npm 对照 |
|---|---|---|
groupId |
组织/公司 | 包的 scope/作者 |
artifactId |
模块名 | 包名 |
version |
版本 | 版本号 |
<groupId>com.javaglm</groupId>
<artifactId>task-manager</artifactId>
<version>1.0.0-SNAPSHOT</version>
SNAPSHOT 表示"开发中快照版",Maven 会每次检查更新;正式版不带它。
pom.xml:本书工程的真实结构¶
本书所有代码在 code/ 下,用 Maven 多模块组织。根 pom(既是聚合器又是父 POM):
<?xml version="1.0" encoding="UTF-8"?>
<!--
聚合根 POM:一条 `mvn -f code/pom.xml compile` 即可编译全部可运行示例。
- 既是聚合器(<modules>):统一构建所有模块
- 也是语言篇模块的父 POM:统一 Java 8 编译基线(release 8,本机 JDK 17 可交叉编译)
注意:task-manager 用 spring-boot-starter-parent 作为自己的 parent,仅作为 module 被聚合。
-->
<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.javaglm</groupId>
<artifactId>javaglm-code</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>JavaGLM 可运行代码(聚合根)</name>
<description>《给 Vue 工程师的 Java 全栈教程》全部可运行示例</description>
<properties>
<!-- 全书代码基线:Java 8 风格。本机 JDK 17 通过 release 8 交叉编译。 -->
<maven.compiler.release>8</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<modules>
<module>language</module>
<module>task-manager</module>
<module>advanced</module>
</modules>
<build>
<!-- 钉死现代 compiler 插件版本:旧版(如 3.1)不支持 release 参数,会回退到 Java 5 -->
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
这段真实文件演示了几个关键点:
<packaging>pom</packaging>:它本身不产 jar,只是个"管理其他模块"的壳。<modules>:聚合——声明要一起构建的子模块(language / task-manager / advanced)。<properties>:集中配置(编译版本、编码),子模块继承。<build><pluginManagement>:钉死插件版本——这是踩过坑的教训:不钉版本会回退到旧插件(比如 3.1 的 compiler 不支持release,编译直接报错)。
★ 生命周期(lifecycle)与阶段(phase)¶
这是 Maven 和 npm 最大区别。Maven 有一套有序的阶段,每个阶段绑定具体动作:
| 命令 | 跑到哪个阶段 | 干什么 |
|---|---|---|
mvn compile |
compile | 编译 src/main |
mvn test |
test | 编译 + 跑测试 |
mvn package |
package | + 打成 jar/war |
mvn install |
install | + 装进本地仓库 |
mvn clean |
— | 删 target/(清理) |
关键认知:阶段是有序的,跑后面的会自动先跑前面的。所以 mvn package 会自动先 compile + test。这就是为什么你 package 时会看到测试在跑。
★ 依赖范围(scope)¶
npm 没有、但 Maven 很重要的概念:一个依赖在什么时候可用。看 task-manager 的真实依赖:
<?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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.javaglm</groupId>
<artifactId>task-manager</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>终局项目 · 任务管理系统(Spring Boot 2.7.x)</name>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<jjwt.version>0.11.5</jjwt.version>
</properties>
<dependencies>
<!-- Web:REST 接口、内置 Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 参数校验:@Valid / @NotBlank -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 数据访问:MyBatis-Plus(第 26 章) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- 密码加密:BCrypt(只引 crypto,不引完整的 spring-security,避免自动拦截所有接口) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<!-- JWT:签发与校验 token(第 29 章) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意各依赖的 <scope>:
| scope | 何时可用 | 例子(task-manager 里) |
|---|---|---|
| compile(默认) | 编译 + 运行 + 打包 | spring-boot-starter-web |
| test | 只在测试时 | spring-boot-starter-test |
| runtime | 运行时(编译不需要) | mysql-connector-java、jjwt-impl |
| provided | 编译需要,运行时由容器提供 | servlet-api(打 war 部署到 Tomcat 时) |
为什么 mysql 标 runtime:你的代码不直接 import MySQL 驱动的类(是 JDBC/MyBatis 在运行时通过反射加载的),所以编译期不需要它。标 runtime 让它不污染编译,但最终包里仍会带上。
传递依赖与冲突¶
加 A,A 依赖 B,B 自动也来了——这叫传递依赖。好处是少写依赖,坏处是可能版本冲突。
冲突时 Maven 用最短路径优先(离你近的版本赢)。遇到诡异的 NoSuchMethodError,多半是版本冲突,dependency:tree 排查。
★ 多模块:聚合 vs 继承¶
这两个概念前端人最容易混。本书仓库就是活教材:
- 聚合(aggregation):根 pom 的
<modules>,让一条命令(mvn -f code/pom.xml compile)构建所有子模块。聚合只管"一起构建"。 - 继承(inheritance):子模块的
<parent>指向父 pom,继承它的 properties、插件配置。
关键:两者独立。一个模块可以"被聚合"但"不继承"。本书 task-manager 就是:它被 code/pom.xml 聚合(在 <modules> 里),但它的 <parent> 用的是 spring-boot-starter-parent(外部),不继承 code/pom.xml。这样它能享受 Spring Boot 的依赖版本管理。
flowchart TB
Root["code/pom.xml\n聚合器 modules + 父POM properties"]
Root -->|聚合| Lang["language/\n继承 Root"]
Root -->|聚合| TM["task-manager/\n被聚合,但 parent=spring-boot"]
Root -->|聚合| Adv["advanced/\n继承 Root"]
本地仓库 ~/.m2 与 settings.xml¶
- 下载的依赖全存在
~/.m2/repository(对标node_modules,但是全局共享,不在每个项目里重复下)。 ~/.m2/settings.xml配镜像(第 02 章配过阿里云)、私服地址、账号。- 公司内部依赖通常放私有 Nexus 仓库,在 settings.xml 配
<repository>或 mirror。
IDEA 里的 Maven¶
- 打开 pom.xml,IDEA 右上角 Maven 刷新按钮(reload):改了 pom.xml 后点它,重新下载/同步依赖。
- 右侧 Maven 面板:双击 lifecycle 的 phase(clean / compile / test / package)直接跑。
- 依赖图(
Show Dependencies):可视化依赖树,找冲突。
命令速查¶
| 想干什么 | 命令 |
|---|---|
| 编译 | mvn compile |
| 跑测试 | mvn test |
| 打包 | mvn clean package |
| 打包跳过测试 | mvn clean package -DskipTests |
| 只构建某个模块 | mvn -pl task-manager -am compile |
| 看依赖树 | mvn dependency:tree |
| 清理 | mvn clean |
至此 Maven 你就够用了。本书后续每章的代码都是这套 Maven 结构里的真实模块,回头对照这一章会越看越明白。
:octicons-arrow-left-16: 上一章:Vue/JS 工程师的 Java 速查表 | 第二篇 · Java 语言基础