乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 介绍与核心概念

    • Maven是什么
    • 约定优于配置
  • 第2章 安装与配置

    • 安装与验证
    • settings.xml
    • 本地仓库与镜像
  • 第3章 POM与项目坐标

    • POM
    • GAV坐标
    • packaging
  • 第4章 标准目录布局

    • 标准目录布局
  • 第5章 依赖机制

    • dependencies
    • scope
    • 依赖传递
    • 依赖冲突与调解
    • exclusions
    • optional
    • dependencyManagement
  • 第6章 仓库

    • 仓库体系
    • 本地仓库
    • 远程仓库与镜像
    • 私服
  • 第7章 构建生命周期

    • 生命周期概述
    • clean 生命周期
    • default 生命周期
    • site 生命周期
    • 生命周期与插件绑定
  • 第8章 插件

    • 插件概述
    • maven-compiler-plugin
    • maven-surefire-plugin
    • maven-war-plugin
  • 第9章 继承与聚合

    • parent继承
    • 聚合
    • BOM
    • properties
  • 第10章 属性与资源过滤

    • 资源过滤
    • Profile
  • 第11章 常用命令

    • mvn compile
    • mvn test
    • mvn package
    • mvn clean
    • mvn install
    • mvn dependency:tree
  • 第12章 常见问题与最佳实践

    • 依赖冲突排查
    • 最佳实践

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 阶段的具体工作

  1. 解析依赖:从本地仓库和远程仓库下载 pom.xml 中声明的依赖
  2. 复制资源:将 src/main/resources 下的资源文件复制到 target/classes
  3. 编译源码:将 src/main/java 下的 .java 文件编译为 .class 文件,输出到 target/classes
  4. 处理注解:如果配置了注解处理器,在编译阶段执行

生活类比:工厂流水线

想象一家食品加工厂(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 文件

排查步骤:

  1. 根据 [行,列] 定位到具体代码位置
  2. 检查该行及前一行是否有遗漏的分号、括号不匹配、类型错误等
  3. 修复后重新执行 mvn compile
  4. 如果错误信息指向的是依赖类找不到,检查 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 将在此基础上,展示如何编译并运行测试代码,验证代码正确性。

下一页
mvn test