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

时间:2016-03-10 10:26:39

标签: java eclipse maven intellij-idea 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>

然而我宣布没有问题:

public enum DirectoryWatchService {

    INSTANCE;

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

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

1 个答案:

答案 0 :(得分:14)

您正在对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

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

如果source / target未与bootclasspath选项合并,请

Since JDK 7, javac prints a warning。在这些情况下,bootclasspath选项是指向所需目标Java版本的rt.jar的关键选项(因此,您需要在计算机中安装目标JDK)。因此,javac将有效地编译好的Java API。

但这可能仍然不够!

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

  

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

因此,即使使用source / targetbootclasspath选项,我们仍可能会在交叉编译过程中遗漏某些内容,正如official example中所述, javac文档。

  

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

但是......它可能仍然不够!

来自Oracle官方documentation

  

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

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

  

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

那么,这真的不可能吗?

Spring 4 currently supports Java 6, 7 and 8。甚至使用Java 7和Java 8功能和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选项。使用可选,Stream和许多其他Java 8 API。只有在运行时检测到JRE 7/8时,它才会根据Java 7或Java 8 API注入bean:s​​mart approach!

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

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

如果您希望仅使用JDK 6 API,则只要使用JDK 7 API,构建就会失败。

官方source/target page of the maven-compiler-plugin

也建议使用此用法
  

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

Java 9更新
在Java 9中,这种机制has been radically changed采用以下方法:

javac --release N ...

在语义上等同于

javac -source N -target N -bootclasspath rtN.jar ...
  
      
  • 有关javac

    可用的早期版本的API的信息      
        
    • 以压缩方式存储
    •   
    • 仅提供平台中立的Java SE N 和JDK N - 出版的API
    •   
  •   
  • -source / -target
  • 相同的发布值 N   
  • 不兼容的选项组合被拒绝
  •   

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

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

         
        
    • 错误修复
    •   
    • 提高速度
    •   
  •   

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

  

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

一个例子:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <release>8</release>
    </configuration>
</plugin>