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

标签 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清洁包。在没有任何警告的情况下构建令人毛骨悚然的东西。

最佳答案

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

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

普遍的信念是,使用两个编译器选项就足够了。但是,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.jarlib\ext文件夹提供了其他类。为此,使用了另一个javac选项extdirs。摘自Oracle官方文档

If you are cross-compiling (compiling classes against bootstrap and extension classes of a different Java platform implementation), this option specifies the directories that contain the extension classes.



因此,即使使用source/targetbootclasspath选项,我们也可能在交叉编译期间错过某些内容,正如javac文档随附的official example中所解释的那样。

The Java Platform JDK's javac would also by default compile against its own bootstrap classes, so we need to tell javac to compile against JDK 1.5 bootstrap classes instead. We do this with -bootclasspath and -extdirs. Failing to do this might allow compilation against a Java Platform API that would not be present on a 1.5 VM and would fail at runtime.



但是..可能还不够!

从Oracle官方documentation

Even when the bootclasspath and -source/-target are all set appropriately for cross-compilation, compiler-internal contracts, such as how anonymous inner classes are compiled, may differ between, say, javac in JDK 1.4.2 and javac in JDK 6 running with the -target 1.4 option.



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

The most reliably way to produce class files that will work on a particular JDK and later is to compile the source files using the oldest JDK of interest. Barring that, the bootclasspath must be set for robust cross-compilation to an older 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注入(inject)bean:聪明的方法!

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

使用Maven Animal Sniffer插件。
该插件检查您的应用程序是否与指定的Java版本兼容。之所以称为动物嗅探器,是因为Sun传统上用different animals来命名不同版本的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>

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

官方 source / target page of the maven-compiler-plugin 中也建议使用此用法

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.



Java 9更新
在Java 9中,此机制has been radically changed采取以下方法:
javac --release N ...

在语义上将等效于
javac -source N -target N -bootclasspath rtN.jar ...

  • Information about APIs of earlier releases available to javac

    • Stored in a compressed fashion
    • Only provide Java SE N and JDK N-exported APIs that are platform neutral
  • Same set of release values N as for -source / -target
  • Incompatible combinations of options rejected

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

  • No user need to manage artifacts storing old API information
  • Should remove need to use tools like the Maven plugin Animal Sniffer
  • May use newer compilation idioms than the javac in older releases

    • Bug fixes
    • Speed improvements


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

The -release argument for the Java compiler, supported since Java9



一个例子:
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <release>8</release>
    </configuration>
</plugin>

关于java - Eclipse/Idea忽略了Maven Java版本配置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35913775/

相关文章:

scala - 项目在 IntelliJ 中编译良好,Tomcat 说 java.lang.NoClassDefFoundError : my/package/name/blah

maven 从当前目录复制单个文件

java - 为什么 NetworkInterface 的方法会抛出 SocketException

java - GWT 中的客户端缓存

java - 如何获取图像文件的相对路径

java - 将 Apache POI 导入 eclipse 项目并使其可以在另一台计算机上使用

java - connect() 后未调用 MediaBrowserCompat.ConnectionCallback.onConnected()

java - org.apache.poi.POIXMLException 目前不支持严格的 OOXML,请参阅 bug #57699

java - 不幸的是,应用程序已停止工作?

maven - m2e 无法使用 Artifactory 解析原型(prototype)