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

Joh*_*ith 8 java eclipse intellij-idea maven maven-compiler-plugin

我有:

<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>
Run Code Online (Sandbox Code Playgroud)

然而我宣布:

public enum DirectoryWatchService {

    INSTANCE;

    private java.util.Optional<String> test;
    private java.nio.file.Files files;
}
Run Code Online (Sandbox Code Playgroud)

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

A_D*_*teo 15

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

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

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

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

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

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

但这可能仍然不够!

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

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

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

Java平台JDK的javac默认也会针对自己的引导类进行编译,因此我们需要告诉javac来编译JDK 1.5引导类.我们使用-bootclasspath和-extdirs执行此操作.如果不这样做,可能允许针对1.5平台上不存在的Java Platform 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 8功能和API.那怎么能兼容Java 7和8 ?!

Spring利用source/ targetbootclasspath灵活性.弹簧4总是编译source/ target语法保持Java的6级:Java 6中,这样的字节码仍然可以JRE 6.因此没有使用Java的7/8语言特性下运行.
但它也使用Java 7和Java 8 API!因此,bootclasspath不使用选项.使用可选,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文件中:

<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>
Run Code Online (Sandbox Code Playgroud)

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

在官方/ 页面中也推荐使用此用法sourcetargetmaven-compiler-plugin

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


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

javac --release N ...
Run Code Online (Sandbox Code Playgroud)

将在语义上等同于

javac -source N -target N -bootclasspath rtN.jar ...
Run Code Online (Sandbox Code Playgroud)
  • 有关早期版本的API的信息 javac

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

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

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

    • Bug修复
    • 速度提升

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

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

一个例子:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <release>8</release>
    </configuration>
</plugin>
Run Code Online (Sandbox Code Playgroud)

  • 关于这个答案的一些小评论.`-source`选项控制编译器接受的源代码的*语言级别*.`-target`选项控制编译器发出的*classfile version*.看起来他们似乎是独立的,但他们真的不是; 依赖性可能非常复杂.例如,编译lambda需要`invokedynamic`字节码,因此`-source 1.8 -target 1.6`根本不起作用. (3认同)
  • 我可能来不及回答,但是maven还提供了工具链:这使您可以使用JDK8执行maven,并使用JDK6进行编译。从某种意义上讲,这种方式比使用源/目标更好:https://maven.apache.org/guides/mini/guide-using-toolchains.html (2认同)