maven toolchains 详解

Last Modified: 2023/07/16

概述

本文将会结合实际场景给大家介绍如何使用 maven toolchains。通过本文你将会掌握如何利用 maven toolchains 来构建需要不同 java 版本的项目。 友情提醒,本文在介绍 maven toolchains 之前有很大的前摇,建议赶时间的同学可以直接跳过“前情回顾”。

前情回顾

自从 Java SE 10 开始,每隔 6 个月就会发行一个新的版本,到如今已经是 Java 20 了,再过两个月就要迎来 Java 21,然而国内很多公司目前仍然在使用 Java 8,“你发任你发,我用 Java 8” 也成了一个段子。Java 8 是 2014 年发布的,再过些年,是不是我们也可以自豪的说 “Java 8 两代 Java 程序员的传承与守望”?

不扯段子了,那天我破天荒的将本地 Java 8 升级到了 Java 17,毕竟 Spring Boot 3.0 要求的最低版本就是 Java 17。下载了 Java 17 后,紧接着就是将 JAVA_HOME 环境变量指向新的 JDK 17 的安装目录。

升级完成之后,幺蛾子随之而来。话说我本地有个老项目,使用的就是 Java 8,当我使用 mvn package 打包这个老项目的时候报错了!为了更好的说明问题,我将报错有关的代码摘录如下:

import sun.misc.BASE64Encoder;
import java.nio.charset.StandardCharsets;

public class Main {
  public static void main(String[] args) {
    String encoded = new BASE64Encoder().encode("hello".getBytes(StandardCharsets.UTF_8));
    System.out.println(encoded);
  }
}

使用 mvn package 打包的报错的信息如下:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project prac-mvn-toolchains: Compilation failure: Compilation failure: 
[ERROR] /home/saltyfish/Codes/prac-mvn-toolchains/src/main/java/net/verytools/prac/Main.java:[3,16] 找不到符号
[ERROR]   符号:   类 BASE64Encoder
[ERROR]   位置: 程序包 sun.misc
[ERROR] /home/saltyfish/Codes/prac-mvn-toolchains/src/main/java/net/verytools/prac/Main.java:[10,30] 找不到符号
[ERROR]   符号:   类 BASE64Encoder
[ERROR]   位置: 类 net.verytools.prac.Main

问题就出在 BASE64Encoder 这个类,从 Java 9 开始,这个类从 rt.jar 中被移除了,而我们这里使用 Java 17 打包项目,因此就报错了。这就引出了一个需求:根据项目实际使用的 Java 版本编译和构建项目。 例如,我们这个项目使用的是 Java 8,因此我们希望 maven 能够使用 JDK8 来编译和打包我们的项目,但实际情况是 maven 仅仅根据 JAVA_HOME 指向的 Java 版本编译项目。

有同学可能会有疑问,难道我在 pom.xml 中指定 Java 版本也不能解决问题吗?答案是不能解决!顺便提一下在 pom.xml 中指定 java 版本的方法如下:

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>

注:maven 指定 Java 版本的方法很多,更多方法参考这里

为什么明明指定了 Java 版本仍然不起作用呢?答案就隐藏在 maven 的文档中,摘录如下:

Note: Merely setting the target option does not guarantee that your code actually runs on a JRE with the specified version. The pitfall is unintended usage of APIs that only exist in later JREs which would make your code fail at runtime with a linkage error. To avoid this issue, you can either configure the compiler's boot classpath to match the target JRE, or use the Animal Sniffer Maven Plugin to verify your code doesn't use unintended APIs, or better yet use the release option supported since JDK 9. In the same way, setting the source option does not guarantee that your code actually compiles on a JDK with the specified version. To compile your code with a specific JDK version, different than the one used to launch Maven, refer to the Compile Using A Different JDK example.

大体意思就是这样还不够,要想编译成功还和 API 有关,具体到我们这里 BASE64Encoder 在 JDK 9 之后就不存在了,因此也就无法编译成功。解决的办法有两种,一种是通过 maven 插件指定 javac 程序的具体位置,另外一种是我们本文重点要讲解的方法,即通过 maven toolchains。在讲解 maven toolchains 之前,我们先说下第一种方式如何实现,具体方法如下:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.11.0</version>
      <configuration>
        <verbose>true</verbose>
        <fork>true</fork>
        <executable>/path/to/jdk8/bin/javac</executable>
        <compilerVersion>1.3</compilerVersion>
      </configuration>
    </plugin>
  </plugins>
</build>

通过 <executable> 指定具体 javac 所在的位置,如果我们想使用 Java 8 来编译项目,那么可以找到 JDK 8 的安装目录下的 bin 目录下的 javac,要根据实际情况设置。 这种方式有个巨大的缺点,每个人安装 Java 8 的目录可能都不一样,在编译项目的时候,每个人都要修改这个配置。

什么是 maven toolchains

Maven Toolchains 提供了一种方式,使得插件可以在构建过程中自动发现要使用的 JDK(或其他工具),而无需在每个插件或每个 pom.xml 中进行配置,也不需要强制指定在每台构建项目的机器上的精确位置。通过将 Maven Toolchains 应用于JDK工具链,项目现在可以使用与 Maven运行的 JDK 版本独立的特定版本的 JDK 进行构建。就像 IDEA、NetBeans 和 Eclipse 这样的集成开发环境中可以设置 JDK 版本一样,或者可以从 Maven 使用较新的JDK编译旧版本的代码。

上面是我翻译自 maven 官方文档,翻译的有点蹩脚,所以有必要解释下,这里有几个重点:

  • Maven Toolchains 提供了统一的方式,但该方式需要插件本身提供支持才能发挥作用,如果某个插件不支持 maven toolchains,那么该插件仍然需要特殊的配置才可以使用特定版本的 Java。好在 maven-compiler-plugin 是支持的;
  • 前面我们介绍了通过配置 javac 的精确路径来使用特定的 JDK 构建项目,该方式的缺点是需要每个需要构建项目的人(不同的机器)指定 javac 的精确路径,但是 maven toolchains 解决了这个问题,具体下面会说明;
  • maven toolchains 可以让运行 maven 的 jdk 版本和项目本身需要的 jdk 版本区别开来。这是最重要的一点,因为项目构建所需要的 jdk 版本往往和 maven 本身运行所需要的 jdk 版本是不同的!

注:maven 本身使用 java 语言编写,所以运行 maven 也是需要 Java 的。但是将 maven 本身运行的所需的 jdk 和项目使用的 jdk 区分开来显然是明智的选择。

需要说明,当前有不少插件能够感知 maven toolchains:

Toolchain type Plugin
jdk maven-compiler-plugin
jdk maven-jarsigner-plugin
jdk maven-javadoc-plugin
jdk maven-pmd-plugin
jdk maven-surefire-plugin
jdk animal-sniffer-maven-plugin
jdk cassandra-maven-plugin
jdk exec-maven-plugin
jdk jdiff-maven-plugin
jdk keytool-maven-plugin
protobuf maven-protoc-plugin

如何使用 maven toolchains

使用 maven toolchains 分为两步,第一步是配置 maven toolchains,第二步在项目中使用 maven toolchains 插件。其中第一步是一次性工作,只需要配置一次,以后其他项目就可以直接使用 maven toolchains。

1、配置 maven toolchains

${user.home}/.m2/ 目录下新建一个 toolchains.xml,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<toolchains>
  <!-- JDK toolchains -->
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.8</version>
      <vendor>oracle</vendor>
    </provides>
    <configuration>
      <jdkHome>/path/to/jdk1.8</jdkHome>
    </configuration>
  </toolchain>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>17</version>
      <vendor>oracle</vendor>
    </provides>
    <configuration>
      <jdkHome>/path/to/jdk17</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

有同学可能会发现,这里面有个 <jdkHome> 配置项,需要指定 JDK 的安装目录,这不还是需要指定 jdk 的精确路径吗?跟前面指定 javac 的精确路径有啥区别?确实是有区别的,这个配置文件只需要配置一次,不需要在每个项目中都配置。也就说大家的 pom.xml 能保持一致。

2、项目中使用 maven toolchains 插件

第二步配置 pom.xml,加入 maven-toolchains-plugin,并指定项目使用的 jdk 版本,配置方法如下:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-toolchains-plugin</artifactId>
  <version>1.1</version>
  <executions>
    <execution>
      <goals>
        <goal>toolchain</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <toolchains>
      <jdk>
        <version>1.8</version>
        <vendor>oracle</vendor>
      </jdk>
    </toolchains>
  </configuration>
</plugin>

可以看到这里配置的 jdk version 是 1.8,vendor 是 oracle,maven 会通过 vendor 和 version 匹配到 toolchains 中的 jdk,从而找到 Java 实际安装路径。

总结

本文介绍了如何通过 maven toolchains 实现编译不同项目使用不同的 jdk 版本。

有问题吗?点此反馈!

温馨提示:反馈需要登录