mvn compile
本章承接
mvn clean,进入 default 生命周期的核心阶段。理解mvn compile的作用和编译机制,是掌握 Maven 如何将 Java 源码转化为可执行字节码的基础。
核心机制
compile 阶段编译项目的主源代码,将 .java 文件编译为 .class 字节码文件,输出到 target/classes 目录。
注意这里的限定词——"main source code",即 src/main/java 下的代码。src/test/java 下的测试代码不在 compile 阶段编译,而是在后续的 test-compile 阶段处理。
compile 阶段在生命周期中的位置
default 生命周期的完整阶段链是:
validate → compile → test-compile → test → package → verify → install → deploy
compile 是第二个阶段,它的前置条件是 validate(验证项目结构和 POM 合法性)。执行 mvn compile 时,Maven 会自动先执行 validate,再执行 compile。
谁在做真正的编译工作?
Maven 本身不编译代码。compile 阶段默认绑定了 maven-compiler-plugin 的 compile 目标,该插件底层调用 JDK 的 javac 命令完成实际编译。
mvn compile
→ 触发 compile 阶段
→ 调用 maven-compiler-plugin:compile
→ 调用 javac
→ 生成 .class 文件到 target/classes/
compile 阶段的具体工作
- 解析依赖:从本地仓库和远程仓库下载
pom.xml中声明的依赖 - 复制资源:将
src/main/resources下的资源文件复制到target/classes - 编译源码:将
src/main/java下的.java文件编译为.class文件,输出到target/classes - 处理注解:如果配置了注解处理器,在编译阶段执行
生活类比:工厂流水线
想象一家食品加工厂(Maven):
src/main/java是原材料仓库,存放着未经加工的面粉、鸡蛋、牛奶(Java 源码)compile阶段是烘焙车间,将原材料按配方(语法规则)加工成面包(.class字节码)target/classes是成品暂存区,面包在这里等待后续包装(package阶段)- 如果原材料有问题(语法错误),烘焙车间的质检员(编译器)会立刻叫停,并指出哪一袋面粉过期了(错误位置和原因)
图示
上图展示了 mvn compile 的完整数据流:源码和资源文件作为输入,经过 validate 和 compiler 插件的处理,输出为 .class 文件和复制过来的资源文件。注意依赖 JAR 不会被复制到 target/classes,它们通过 classpath 引用。
完整示例
场景
飞翔科技的 employee-system 项目中,后端小崔刚写完 EmployeeService.java,需要编译验证代码是否有语法错误。
操作前:项目状态
src/main/java/com/feixiang/service/EmployeeService.java:
package com.feixiang.service;
import com.feixiang.model.Employee;
import java.util.List;
import java.util.ArrayList;
public class EmployeeService {
private List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee emp) {
employees.add(emp);
}
public List<Employee> getAllEmployees() {
return employees;
}
public Employee findById(Long id) {
return employees.stream()
.filter(e -> e.getId().equals(id))
.findFirst()
.orElse(null);
}
}
pom.xml 中的编译器配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
执行编译前,target/ 目录不存在(或已被 mvn clean 删除)。
操作步骤
步骤一:执行编译
mvn compile
步骤二:观察输出
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.feixiang:employee-system >------------------
[INFO] Building employee-system 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ employee-system ---
[INFO] Copying 1 resource from src\main\resources to target\classes
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ employee-system ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to C:\Users\xiaocui\workspace\employee-system\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.145 s
[INFO] Finished at: 2024-01-15T11:00:00+08:00
[INFO] ------------------------------------------------------------------------
关键输出解读:
resources:3.3.1:resources—— 资源插件先复制资源文件compiler:3.11.0:compile—— 编译器插件执行编译Compiling 5 source files—— 编译了 5 个 Java 源文件target\classes—— 编译输出目录
compile 前后的 target 目录变化
编译前:
employee-system/
├── pom.xml
├── src/
│ └── main/
│ ├── java/
│ │ └── com/feixiang/
│ │ ├── service/EmployeeService.java
│ │ ├── model/Employee.java
│ │ ├── dao/EmployeeDao.java
│ │ ├── controller/EmployeeController.java
│ │ └── EmployeeSystemApplication.java
│ └── resources/
│ └── application.properties
└── (target/ 不存在)
编译后:
employee-system/
├── pom.xml
├── src/
│ └── ...(源码不变)
└── target/
└── classes/
├── com/feixiang/
│ ├── service/EmployeeService.class
│ ├── model/Employee.class
│ ├── dao/EmployeeDao.class
│ ├── controller/EmployeeController.class
│ └── EmployeeSystemApplication.class
└── application.properties
编译错误排查
假设小崔不小心写了一个语法错误:
public class EmployeeService {
public void addEmployee(Employee emp) {
employees.add(emp) // 缺少分号
}
}
执行 mvn compile 后的输出:
[INFO] --- compiler:3.11.0:compile (default-compile) @ employee-system ---
[INFO] Changes detected - recompiling the module!
[INFO] -----------------------------------------------------------
[ERROR] COMPILATION ERROR
[INFO] -----------------------------------------------------------
[ERROR] /C:/Users/xiaocui/workspace/employee-system/src/main/java/com/feixiang/service/EmployeeService.java:[14,32] ';' expected
[INFO] 1 error
[INFO] -----------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
错误信息解读:
[14,32]—— 第 14 行,第 32 列';' expected—— 编译器期望一个分号BUILD FAILURE—— 编译失败,不会生成任何.class文件
排查步骤:
- 根据
[行,列]定位到具体代码位置 - 检查该行及前一行是否有遗漏的分号、括号不匹配、类型错误等
- 修复后重新执行
mvn compile - 如果错误信息指向的是依赖类找不到,检查
pom.xml中是否声明了对应依赖
易错点与常见问题
误区一:compile 会编译测试代码
错误认知:"我执行 mvn compile 后,src/test/java 下的测试类也被编译了。"
纠正:mvn compile 只编译 src/main/java,不编译测试代码。测试代码的编译由 test-compile 阶段负责,该阶段在 test 阶段之前自动执行。如果你看到 target/test-classes/ 出现,说明你执行的是 mvn test 或更后面的阶段,而不是单纯的 mvn compile。
误区二:修改源码后必须 clean 再 compile
错误认知:"我改了 EmployeeService.java,必须先 mvn clean 再 mvn compile,否则旧 class 文件会干扰。"
纠正:maven-compiler-plugin 支持增量编译。它会比较源文件和 .class 文件的时间戳,只编译有变化的文件。日常开发中,直接 mvn compile 即可,无需每次都 clean。只有在以下情况才需要 clean:
- 删除了一个 Java 类(旧
.class需要被清除) - 修改了注解处理器配置
- 切换了 JDK 版本
- 编译出现诡异行为,怀疑是旧产物干扰
误区三:compile 阶段会下载依赖
错误认知:"mvn compile 只编译代码,不会下载依赖。"
纠正:mvn compile 会触发依赖解析。如果 pom.xml 中声明的依赖在本地仓库中不存在,Maven 会自动从远程仓库下载。下载日志通常出现在编译输出之前:
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/...
[INFO] Downloaded from central: ... (1.2 MB at 2.5 MB/s)
误区四:source/target 版本设置无效
典型问题:小崔在 pom.xml 中设置了 <source>17</source>,但编译报错 invalid source release 17。
纠正:source 和 target 配置的是源代码语法版本和字节码目标版本,它们不能超过当前运行 Maven 的 JDK 版本。如果你用 JDK 11 运行 Maven,却设置 <target>17</target>,编译器会报错。解决方法是升级 JDK,或降低 target 版本:
<!-- 错误:JDK 11 无法编译 target 17 -->
<source>17</source>
<target>17</target>
<!-- 正确:与运行 Maven 的 JDK 版本匹配 -->
<source>11</source>
<target>11</target>
小结
mvn compile 是 Maven default 生命周期中编译主源码的核心命令。它通过 maven-compiler-plugin 调用 javac,将 src/main/java 转换为 target/classes 中的 .class 文件。核心要点:
compile只编译主代码,不编译测试代码- 支持增量编译,日常开发无需每次都 clean
- 编译前会自动解析并下载缺失的依赖
<source>/<target>必须与运行 Maven 的 JDK 版本兼容- 编译错误会精确定位到
[行,列],按提示修复即可
本章与全局的关系:本章讲解了源码编译。下一章 mvn test 将在此基础上,展示如何编译并运行测试代码,验证代码正确性。