Eclipse/Idea 忽略 Maven Java 版本配置

2024-05-04

I have:

<build>
  <pluginManagement>
     <plugins>
        <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.1</version>
           <configuration>
              <source>1.6</source>
              <target>1.6</target>
           </configuration>
        </plugin>
     </plugins>
  </pluginManagement>
</build>

但我毫无疑问地宣布:

public enum DirectoryWatchService {

    INSTANCE;

    private java.util.Optional<String> test;
    private java.nio.file.Files files;
}

Eclipse 不打扰。 IntelliJ 也没有。连 Maven 也懒得理会。 我什至可以做一个mvn清理包。在没有任何警告的情况下构建该死的东西。


您遇到了交叉编译的误解source/target选项。在类路径中使用主要版本(JDK 7 或 8),但希望针对次要版本(在您的情况下为 6)进行编译。
编译没问题,但是你会出现错误runtime (i.e. NoClassDefFoundError https://docs.oracle.com/javase/7/docs/api/java/lang/NoClassDefFoundError.html or NoSuchMethodError https://docs.oracle.com/javase/7/docs/api/java/lang/NoSuchMethodError.html,可能也是更通用的LinkageError https://docs.oracle.com/javase/7/docs/api/java/lang/LinkageError.html).

Using source/target http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html#options,Java 编译器可以用作交叉编译器来生成可在实现早期版本的 Java SE 规范的 JDK 上运行的类文件。

人们普遍认为使用两个编译器选项就足够了。但是,那source选项指定我们正在编译的版本target选项指定要支持的最低 Java 版本。

编译器致力于字节码生成和source and target用于在交叉编译期间生成兼容的字节码。然而,Java API 不是由编译器处理的(它们是作为 JDK 安装的一部分提供的,著名的rt.jar文件)。编译器对 API 没有任何了解,它只是根据当前的 API 进行编译rt.jar。因此,当编译时target=1.6使用JDK 1.7,编译器仍会指向JDK 7rt.jar.

那么,我们怎样才能真正进行正确的交叉编译呢?

从 JDK 7 开始,javac 会打印警告 https://blogs.oracle.com/darcy/entry/bootclasspath_older_source的情况下source/target不与bootclasspath选项。这bootclasspath选项是在这些情况下指向的关键选项rt.jar所需的目标 Java 版本(因此,您需要在计算机中安装目标 JDK)。像这样,javac将有效地针对良好的 Java API 进行编译。

但这可能还不够!

并非所有 Java API 都来自rt.jar。其他课程由lib\ext文件夹。进一步javac选项用于此目的,extdirs。来自 Oracle 官方文档

如果您正在进行交叉编译(针对不同 Java 平台实现的引导类和扩展类进行编译),则此选项指定包含扩展类的目录。

因此,即使使用source/target and bootclasspath选项,我们在交叉编译过程中仍然可能会错过一些东西,正如官方示例 http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html#crosscomp-example随 javac 文档一起提供。

Java 平台 JDK 的 javac 默认情况下也会针对其自己的引导类进行编译,因此我们需要告诉 javac 针对 JDK 1.5 引导类进行编译。我们使用 -bootclasspath 和 -extdirs 来完成此操作。如果不这样做,可能会允许针对 Java 平台 API 进行编译,而该 API 不会出现在 1.5 VM 上,并且会在运行时失败。

但是..可能还不够!

来自甲骨文官方文档 https://blogs.oracle.com/darcy/entry/how_to_cross_compile_for

即使 bootclasspath 和 -source/-target 都为交叉编译进行了适当设置,编译器内部约定(例如匿名内部类的编译方式)可能会有所不同,例如 JDK 1.4.2 中的 javac 和 JDK 6 中的 javac使用 -target 1.4 选项运行。

建议的解决方案(来自甲骨文官方 https://blogs.oracle.com/darcy/entry/how_to_cross_compile_for文档)

生成可在特定 JDK 及更高版本上运行的类文件的最可靠方法是使用最旧的 JDK 来编译源文件。除此之外,必须设置 bootclasspath 才能对旧版 JDK 进行稳健的交叉编译。

那么,这真的不可能吗?

Spring 4 目前支持 Java 6、7 和 8 https://spring.io/blog/2015/04/03/how-spring-achieves-compatibility-with-java-6-7-and-8。甚至使用 Java 7 和 Java 8 功能和 API。那它怎么能兼容Java 7和8呢?!

Spring利用source/target and bootclasspath灵活性。 Spring 4 总是编译source/target到 Java 6,以便字节码仍然可以在 JRE 6 下运行。因此,不使用 Java 7/8 语言功能:语法保持 Java 6 水平。
但它也使用 Java 7 和 Java 8 API!因此,bootclasspath不使用选项。使用了可选的 Stream 和许多其他 Java 8 API。然后,仅当运行时检测到 JRE 7/8 时,它才会根据 Java 7 或 Java 8 API 注入 Bean:聪明的方法!

那么 Spring 如何保证 API 兼容性呢?

使用Maven 动物嗅探器 http://www.mojohaus.org/animal-sniffer/animal-sniffer-maven-plugin/ plugin.
该插件检查您的应用程序是否与指定的 Java 版本 API 兼容。被称为动物嗅探器,因为 Sun 传统上以不同版本的 Java 命名不同的动物 http://www.oracle.com/technetwork/java/javase/codenames-136090.html(Java 4 = 梅林(鸟),Java 5 = 老虎,Java 6 = 野马(马),Java 7 = 海豚,Java 8 = 无动物)。

您可以将以下内容添加到 POM 文件中:

<build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.14</version>
        <configuration>
          <signature>
            <groupId>org.codehaus.mojo.signature</groupId>
            <artifactId>java16</artifactId>
            <version>1.0</version>
          </signature>
        </configuration>
        <executions>
          <execution>
            <id>ensure-java-1.6-class-library</id>
            <phase>verify</phase>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
</build>

当您使用 JDK 7 API 而希望仅使用 JDK 6 API 时,构建将会失败。

官方也推荐这个用法source/target的页面maven-compiler-plugin https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-source-and-target.html

Note: 只需设置target选项不保证您的代码实际上在指定版本的 JRE 上运行。陷阱是意外使用仅存在于后续 JRE 中的 API,这会使您的代码在运行时失败并出现链接错误。为了避免此问题,您可以配置编译器的启动类路径以匹配目标 JRE,或者使用 Animal Sniffer Maven 插件来验证您的代码没有使用意外的 API。


Java 9 更新
在Java 9中这个机制已被彻底改变 https://blogs.oracle.com/darcy/resource/JavaOne/J1_2016-jdk9-lang-tools-libs.pdf采用以下方法:

javac --release N ...

在语义上等同于

javac -source N -target N -bootclasspath rtN.jar ...
  • 有关可用的早期版本 API 的信息javac

    • 以压缩方式存储
    • 只提供Java SEN和JDKN-导出的API是平台中立的
  • 同一组释放值N as for -source / -target
  • 不兼容的选项组合被拒绝

主要优点--release N方法:

  • 用户无需管理存储旧 API 信息的工件
  • 应该不再需要使用 Maven 插件 Animal Sniffer 等工具
  • 可能会使用比以下更新的编译习惯用法javac在旧版本中

    • Bug修复
    • 速度改进

Java 9 和 Maven 更新
自版本以来3.6.0, the maven-compiler-plugin通过其提供对 Java 9 的支持release https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#release option:

The -releaseJava 编译器的参数,从 Java9 开始支持

一个例子:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <release>8</release>
    </configuration>
</plugin>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Eclipse/Idea 忽略 Maven Java 版本配置 的相关文章

随机推荐