maven-compiler-plugin
本章是"插件"章节的第一个具体插件讲解。
maven-compiler-plugin是 Maven 项目中最常用、最基础的插件——它决定了你的代码用什么版本的 Java 编译、生成什么版本的字节码。理解它的配置方式,是避免"本地能跑,线上报错"类问题的关键。
核心机制
虽然描述很简短,但这个插件背后有两个关键决策点:
- source:你的源代码使用哪个 Java 版本的语言特性(如 Java 8 的 Lambda、Java 16 的
record) - target:编译生成的
.class文件兼容哪个 JVM 版本运行
这两个参数如果不一致,会导致编译通过但运行时崩溃的诡异问题。
source 与 target 的区别
| 参数 | 含义 | 示例 |
|---|---|---|
source | 源代码允许的 Java 语言级别 | source=17 允许使用 var、switch 表达式 |
target | 生成字节码的 JVM 兼容级别 | target=8 表示 .class 文件可在 Java 8 JVM 上运行 |
关键规则:source 可以高于 target,但不推荐。例如 source=11、target=8 在语法上允许,但如果代码里用了 Java 11 的特性(如 String.isBlank()),生成的字节码在 Java 8 JVM 上运行时会抛出 NoSuchMethodError。
更安全的做法是两者保持一致,或者使用 release 参数(Java 9+ 推荐):
<configuration>
<release>17</release>
</configuration>
release 同时约束了 source 和 target,并且会检查是否使用了该版本标准库中不存在的 API。
encoding 配置:被忽视的乱码元凶
如果不在 pom.xml 中显式声明编码,Maven 会使用平台默认编码(Windows 上通常是 GBK,Linux/macOS 上通常是 UTF-8)。这意味着:
- 小崔在 Windows 上编译通过的代码,黄俪在 Mac 上编译可能报错"非法字符"
- 源代码中的中文注释或字符串字面量,在不同操作系统下可能被错误解析
最佳实践:所有 Maven 项目都应显式声明 UTF-8 编码。
两种配置方式对比
Maven 提供了两种配置编译器参数的方式:
| 方式 | 写法 | 适用场景 |
|---|---|---|
| Properties 方式 | 在 <properties> 中定义 maven.compiler.source 等 | 简单项目,配置少 |
| Plugin 显式配置 | 在 <plugins> 中完整声明插件 | 复杂项目,需要更多编译选项 |
生活类比:翻译官与目标读者
想象 maven-compiler-plugin 是一位翻译官:
- source = 原文语言。
source=17表示"原文是当代中文,可以用网络流行语"。 - target = 译文语言。
target=8表示"请把文章翻译成 1995 年的中文,不要用那时候还没有的网络流行语"。 - encoding = 字符集。如果翻译官用 GBK 读你的 UTF-8 手稿,所有emoji都会变成乱码。
如果你让翻译官把一篇用了"绝绝子"的文章翻译成 1995 年的中文,他要么报错"这个词当时不存在"(release 模式),要么强行翻译但读者看完一脸懵(运行时崩溃)。
图示
上图展示了编译器插件的工作流程:源代码 + 编码声明 → 插件按 source/target 参数编译 → 生成对应版本的字节码。三个参数缺一不可,任何一个配置错误都会导致编译失败或运行时异常。
完整示例
场景
飞翔科技的 payroll-service(薪资服务)项目最初用 Java 8 开发,现在 CTO 大翔决定升级到 Java 17。后端小崔负责修改编译配置,确保所有开发环境(Windows、Linux、Mac)编译结果一致。
操作前:Java 8 配置
当前 pom.xml 使用 properties 方式配置 Java 8:
<?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>payroll-service</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
小崔写了一个使用 Java 17 record 的类:
package com.feixiang.payroll;
public record Employee(String name, double salary) {}
在当前配置下编译:
$ mvn clean compile
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR
[INFO] -------------------------------------------------------------
[ERROR] /src/main/java/com/feixiang/payroll/Employee.java:[3,8] 错误: 记录是 Java 16 中的预览功能, 默认情况下禁用
原因:source=1.8 不允许使用 record 语法。
操作步骤:升级到 Java 17(Plugin 显式配置)
白歌建议改用 plugin 显式配置,以便后续添加更多编译选项:
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
操作结果:编译前后字节码版本对比
再次编译:
$ mvn clean compile
[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ payroll-service ---
[INFO] Compiling 5 source files to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
查看生成的字节码版本:
$ javap -verbose target/classes/com/feixiang/payroll/Employee.class | grep "major version"
major version: 61
版本对照表:
| Java 版本 | 字节码 major version |
|---|---|
| Java 8 | 52 |
| Java 11 | 55 |
| Java 17 | 61 |
变化分析:
- 小崔的
Employee.java现在可以正常编译,因为source=17支持record - 生成的
.class文件 major version 为 61,对应 Java 17,只能在 Java 17+ 的 JVM 上运行 - 黄俪在 Mac 上执行同样的命令,结果完全一致,因为
encoding=UTF-8消除了平台差异 - 李眉在部署时发现,如果生产环境还是 Java 8,启动时会报错
Unsupported class file major version 61,这促使运维团队同步升级 JDK
易错点与常见问题
误区一:source 和 target 写反了,或者只写一个
错误配置:
<configuration>
<target>17</target>
</configuration>
后果:source 默认继承自 target,但某些旧版本插件中两者可能不同步。更危险的是,如果 source=8、target=17,代码里用了 Java 17 的 var,编译器在 source=8 模式下会直接报错。最佳实践是两者始终一致,或者直接用 release 参数。
误区二:用 properties 方式配置了,又用 plugin 方式重复配置
错误配置:
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
后果:plugin 显式配置的优先级高于 properties。最终生效的是 17,而不是 11。这种"双重配置"会让团队成员困惑,不知道哪个值是真的。建议统一使用一种方式:简单项目用 properties,复杂项目用 plugin。
误区三:升级了 JDK,但没改 compiler 插件配置
错误认知:"我电脑上装了 Java 17,所以 Maven 会自动用 Java 17 编译。"
纠正:Maven 不会自动检测你安装了哪个 JDK。它只认 pom.xml 里的配置。如果你的 pom.xml 还是 source=1.8,即使本地 JDK 是 17,编译器也会按 Java 8 的规则编译。要利用新 JDK 的语言特性,必须显式修改 source/target 或 release 参数。
小结
maven-compiler-plugin 是 Maven 项目的基石插件,它通过 source、target(或 release)和 encoding 三个核心参数,决定了源代码的编译规则。Properties 方式适合简单场景,Plugin 显式配置适合需要精细控制的场景。无论哪种方式,显式声明版本和编码都是生产环境的最佳实践,它能避免"本地能跑、线上报错"的跨平台编译问题。
本章与全局的关系:本章讲解了最基础的编译插件。下一节将讲解 maven-surefire-plugin,它负责在编译之后运行测试,是保障代码质量的第一道防线。