Eclipse / Idea忽略了Maven Java版本配置

我有:

    org.apache.maven.plugins maven-compiler-plugin 3.1  1.6 1.6      

但我宣布:没有问题:

 public enum DirectoryWatchService { INSTANCE; private java.util.Optional test; private java.nio.file.Files files; } 

Eclipse不打扰。 IntelliJ既不是。 即使是Maven也不会打扰。 我甚至可以做一个mvn清洁包 。 在没有任何警告的情况下构建健康的东西。

您正在遇到交叉编译对source / target选项的误解。 在类路径中使用主要版本(JDK 7或8)但希望针对次要版本进行编译(在您的情况下为6)。
编译会很好,但是你会在运行时遇到错误(即NoClassDefFoundErrorNoSuchMethodError ,也可能是更通用的LinkageError )。

使用source / target ,Java编译器可以用作交叉编译器,以生成可在JDK上运行的类文件,这些文件实现了早期版本的Java SE规范。

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

编译器用于字节码生成, sourcetarget用于在交叉编译期间生成兼容的字节码。 但是,Java API不由编译器处理(它们是作为JDK安装的一部分提供的,着名的rt.jar文件)。 编译器没有任何API知识,它只是编译当前的rt.jar 。 因此,当使用JDK 1.7编译target=1.6 ,编译器仍将指向JDK 7 rt.jar

那么,我们怎样才能真正实现正确的交叉编译?

从JDK 7开始,如果source / target不与bootclasspath选项结合使用,javac会发出警告 。 在这些情况下, bootclasspath选项是指向所需目标Java版本的rt.jar的关键选项(因此,您需要在计算机中安装目标JDK)。 因此, javac将有效地编译好的Java API。

但这可能仍然不够!

并非所有Java API都来自rt.jarlib\ext文件夹提供其他类。 另外一个javac选项用于extdirs 。 来自官方Oracle文档

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

因此,即使使用source / targetbootclasspath选项,我们仍然可能在交叉编译期间遗漏一些内容,正如javac文档附带的官方示例中所述。

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

但是……它可能还不够!

来自Oracle官方文档

即使bootclasspath和-source / -target都被适当地设置用于交叉编译,编译器内部契约(例如匿名内部类的编译方式)也可能在JDK 1.4.2中的javac和JDK 6中的javac之间有所不同。使用-target 1.4选项运行。

建议的解决方案(来自Oracle官方文档)

生成可在特定JDK上运行的类文件的最可靠方法是使用最旧的JDK编译源文件。 除此之外,必须设置bootclasspath以便对较旧的JDK进行可靠的交叉编译。

那么,这真的不可能吗?

Spring 4目前支持Java 6,7和8 。 甚至使用Java 7和Java 8function和API。 那么它如何与Java 7和8兼容?

Spring使用source / targetbootclasspath灵活性。 Spring 4总是使用source / target编译到Java 6,因此字节码仍然可以在JRE 6下运行。因此,没有使用Java 7/8语言特性:语法保持Java 6级别。
但它也使用Java 7和Java 8 API! 因此,不使用bootclasspath选项。 使用Optional,Stream和许多其他Java 8 API。 只有在运行时检测到JRE 7/8时,才会根据Java 7或Java 8 API注入bean:智能方法!

但是Spring如何保证API的兼容性呢?

使用Maven Animal Sniffer插件。
此插件检查您的应用程序是否与指定的Java版本API兼容。 被称为动物嗅探器,因为Sun传统上在不同动物之后命名不同版本的Java(Java 4 = Merlin(鸟),Java 5 = Tiger,Java 6 = Mustang(马),Java 7 = Dolphin,Java 8 =无动物)。

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

    org.codehaus.mojo animal-sniffer-maven-plugin 1.14   org.codehaus.mojo.signature java16 1.0     ensure-java-1.6-class-library verify  check       

一旦使用JDK 7 API,构建将失败,同时希望仅使用JDK 6 API。

maven-compiler-plugin的官方source / target页面中也建议使用此用法

注意 :仅设置target选项并不能保证您的代码实际上在具有指定版本的JRE上运行。 缺点是无意中使用的API只存在于以后的JRE中,这会使您的代码在运行时因链接错误而失败。 要避免此问题,可以配置编译器的引导类路径以匹配目标JRE,也可以使用Animal Sniffer Maven插件validation代码是否使用非预期的API。


Java 9更新
在Java 9中,这种机制已从根本上改为以下方法:

 javac --release N ... 

将在语义上等同于

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

    • 以压缩方式存储
    • 仅提供平台中立的Java SE N和JDK N -exported API
  • -source / -target相同的发布值N
  • 拒绝不兼容的选项组合

--release N方法的主要优点:

  • 没有用户需要管理存储旧API信息的工件
  • 应该删除需要使用像Maven插件Animal Sniffer这样的工具
  • 在旧版本中可能使用比javac更新的编译习语

    • Bug修复
    • 速度提升

Java 9和Maven上的更新
从版本3.6.0maven-compiler-plugin通过其release选项提供对Java 9的支持:

自Java9以来支持Java编译器的-release参数

一个例子:

  maven-compiler-plugin 3.6.0  8